diff --git a/.gitignore b/.gitignore index 75b1186b..8b499691 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,12 @@ Intermediate/* # Cache files for the editor to use DerivedDataCache/* +Source/RTSProject/Binaries/ +Source/RTSProject/Intermediate/ +Source/RTSProject/Plugins/RealTimeStrategy/Binaries/ +Source/RTSProject/Plugins/RealTimeStrategy/Intermediate/ +Source/RTSProject/Saved/ +Source/RTSProject/Plugins/RealTimeStrategyTests/Binaries/ +Source/RTSProject/Plugins/RealTimeStrategyTests/Intermediate/ +Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Binaries/ +Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Intermediate/ diff --git a/Assets/Fonts/LICENSE_OFL.txt b/Assets/Fonts/LICENSE_OFL.txt new file mode 100644 index 00000000..d952d62c --- /dev/null +++ b/Assets/Fonts/LICENSE_OFL.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Assets/Fonts/NotoSansDisplay-Bold.ttf b/Assets/Fonts/NotoSansDisplay-Bold.ttf new file mode 100644 index 00000000..b4c6b4e0 Binary files /dev/null and b/Assets/Fonts/NotoSansDisplay-Bold.ttf differ diff --git a/Assets/Fonts/NotoSansDisplay-Regular.ttf b/Assets/Fonts/NotoSansDisplay-Regular.ttf new file mode 100644 index 00000000..89b23f69 Binary files /dev/null and b/Assets/Fonts/NotoSansDisplay-Regular.ttf differ diff --git a/Assets/Fonts/README b/Assets/Fonts/README new file mode 100644 index 00000000..d2287649 --- /dev/null +++ b/Assets/Fonts/README @@ -0,0 +1,11 @@ +This package is part of the noto project. Visit +google.com/get/noto for more information. + +Built on 2017-10-24 from the following noto repository: +----- +Repo: noto-fonts +Tag: v2017-10-24-phase3-second-cleanup +Date: 2017-10-24 12:10:34 GMT +Commit: 8ef14e6c606a7a0ef3943b9ca01fd49445620d79 + +Remove some files that aren't for release. diff --git a/Assets/Meshes/SM_CMV.blend b/Assets/Meshes/SM_CMV.blend new file mode 100644 index 00000000..3ee95ae8 Binary files /dev/null and b/Assets/Meshes/SM_CMV.blend differ diff --git a/Assets/Meshes/SM_CMV.fbx b/Assets/Meshes/SM_CMV.fbx new file mode 100644 index 00000000..f1a85d21 Binary files /dev/null and b/Assets/Meshes/SM_CMV.fbx differ diff --git a/Assets/Meshes/SM_OreAsteroid.blend b/Assets/Meshes/SM_OreAsteroid.blend new file mode 100644 index 00000000..df90a4be Binary files /dev/null and b/Assets/Meshes/SM_OreAsteroid.blend differ diff --git a/Assets/Meshes/SM_OreAsteroid.fbx b/Assets/Meshes/SM_OreAsteroid.fbx new file mode 100644 index 00000000..f1af20b0 Binary files /dev/null and b/Assets/Meshes/SM_OreAsteroid.fbx differ diff --git a/Assets/Meshes/SM_Starbase.blend b/Assets/Meshes/SM_Starbase.blend new file mode 100644 index 00000000..79d64969 Binary files /dev/null and b/Assets/Meshes/SM_Starbase.blend differ diff --git a/Assets/Meshes/SM_Starbase.fbx b/Assets/Meshes/SM_Starbase.fbx new file mode 100644 index 00000000..3d9de2d9 Binary files /dev/null and b/Assets/Meshes/SM_Starbase.fbx differ diff --git a/Assets/Textures/T_Space.png b/Assets/Textures/T_Space.png new file mode 100644 index 00000000..e83dc9c6 Binary files /dev/null and b/Assets/Textures/T_Space.png differ diff --git a/Assets/Textures/T_Space.xcf b/Assets/Textures/T_Space.xcf new file mode 100644 index 00000000..43c1b224 Binary files /dev/null and b/Assets/Textures/T_Space.xcf differ diff --git a/Assets/UI/T_CMV_Portrait.png b/Assets/UI/T_CMV_Portrait.png new file mode 100644 index 00000000..d4d8df0f Binary files /dev/null and b/Assets/UI/T_CMV_Portrait.png differ diff --git a/Assets/UI/T_OreAsteroid_Portrait.png b/Assets/UI/T_OreAsteroid_Portrait.png new file mode 100644 index 00000000..7cde3dbc Binary files /dev/null and b/Assets/UI/T_OreAsteroid_Portrait.png differ diff --git a/Assets/UI/T_Starbase_Portrait.png b/Assets/UI/T_Starbase_Portrait.png new file mode 100644 index 00000000..f2af488e Binary files /dev/null and b/Assets/UI/T_Starbase_Portrait.png differ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..458bc663 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing + +You'd like to help make this plugin even more awesome? Seems like today's our lucky day! In order to maintain stability of the tool and its code base, please adhere to the following steps, and we'll be pleased to include your additions in our next release. + +Note that this plugin is distributed under the [MIT License](https://github.com/npruehs/ue4-rts/blob/develop/LICENSE). So will be your code. + +## How to contribute + +### Step 1: Choose what to do + +If you've got no idea how to help, head over to our [issue tracker](https://github.com/npruehs/ue4-rts/issues) and see what you'd like to do most. You can basically pick anything you want to, as long as it's not already assigned to anyone. + +If you know exactly what you're missing, [open a new issue](https://github.com/npruehs/ue4-rts/issues/new) to begin a short discussion about your idea and how it fits the project. If we all agree, you're good to go! + +### Step 2: Fork the project and check out the code + +Real-Time Strategy Plugin for Unreal Engine 4 is developed using the [GitFlow branching model](http://nvie.com/posts/a-successful-git-branching-model/). In order to contribute, you should check out the latest `develop` branch, and create a new feature or hotfix branch to be merged back. + +### Step 3: Implement your feature or bugfix + +Clearly, everybody's got their own approach here. However, we'd still like you to keep a few things in mind, to ensure the stability and consistency of the plugin for everyone: + +* We're using the official [Coding Standard](https://docs.unrealengine.com/latest/INT/Programming/Development/CodingStandard/index.html) provided by Epic Games. +* When you're adding support for a newer engine version, make sure that you don't break support for previously supported engine versions. We must not force our users to upgrade their engine just because of updating the plugin. + +### Step 4: Open a pull request + +Finally, [open a pull request](https://help.github.com/articles/creating-a-pull-request/) so we can review your changes together, and finally integrate it into the next release. + + +## Release Checklist + +Internally, we're using the following checklist when preparing for a new release: + +* Check pending pull requests +* Create release branch +* Add examples for new features where appropriate +* Run all automated tests +* Update documentation (README, images, spelling, table of contents) +* Increase version number (and engine version, if necessary) +* Create plugin package +* Check plugin package in another project +* Merge release branch with tag +* Add a new GitHub release with release notes +* Update GitHub issues and milestones +* Notify community (e.g. forums) diff --git a/Documents/Manual/AIPlayers.md b/Documents/Manual/AIPlayers.md new file mode 100644 index 00000000..1ca882fc --- /dev/null +++ b/Documents/Manual/AIPlayers.md @@ -0,0 +1,12 @@ +## AI Players + +The plugin provides basic support for AI players as well. Currently, this doesn't go beyond fulfilling basic build orders, so you'll probably want to extend that, e.g. by determining when to attack other players, and where. At least, this should get you started: + +1. Create an AI controller deriving from `RTSPlayerAIController`. +1. Set the _Player Behavior Tree Asset_ of your new player AI controller to `BT_RTSPlayerBehaviorTree` (or create your own one). +1. Set the _Player Blackboard Asset_ of your new player AI controller to `BB_RTSPlayerBlackboard` (or create your own one). +1. Set up the _Build Order_ of your new player AI controller. The AI will produce the specified actors in order, automatically skipping actors that are already available and replacing those that have been destroyed. +1. Set up the _Primary Resource Type_ of your new player AI controller. The AI will try and prevent blocking paths between its main building and resource sources of that type. +1. Add your resource types to the `PlayerResourcesComponent` of your player AI controller. +1. Use your player AI controller in your game mode. +1. At your game mode, set _Num AI Players_ to the number of AI players you want to spawn. diff --git a/Documents/Manual/Buildings.md b/Documents/Manual/Buildings.md new file mode 100644 index 00000000..5849002b --- /dev/null +++ b/Documents/Manual/Buildings.md @@ -0,0 +1,33 @@ +## Creating Buildings + +From the perspective of the plugin, buildings are just units with a few additional components. There's no special class for buildings; their setup has just been moved to this manual section because that many people would explicitly look for that. In fact, you can mix and match the setup outlined in this section with all of the other sections. This allows you to create truly deep gameplay, such as units that serve as resource sources, or produce other units. + + +### Construction + +1. See [Creating Units](Units.md) (Appearance, Health & Damage, Projectiles). +1. Add an `RTSConstructionSiteComponent` and set the _Construction Time_. +1. Set the _Construction Costs_ to any resources required for construction. +1. Set the _Construction Cost Type_ to to _Pay Immediately_ if all costs should be paid in full when starting construction, or to _Pay Over Time_ for continuously paying costs (similar to Command & Conquer). +1. Set the _Refund Factor_ to the factor to multiply refunded resources with after cancelation. +1. Set the _Consumes Builders_ flag if builders working at the construction site should be destroyed when finished (similar to Zerg in StarCraft). +1. Set _Max Assigned Builders_ if you want to require a builder to work at the construction site to make progress, and/or to allow multi-building (similar to Age of Empires). +1. Set the _Progress Made Automatically_ and _Progress Made Per Builder_ factors. +1. Set the _Start Immediately_ flag unless you want to trigger construction start from script. +1. Add an `RTSContainerComponent` if you want builders to enter the building site while building. Its capacity value will be automatically set at runtime to reflect _Max Assigned Builders_ of the construction site. +1. Add your `RTSConstructionProgressBarWidgetComponent` (see [User Interface](UserInterface.md)). + +### Production + +1. Add an `RTSProductionComponent` to any actors you want to be able to produce units or research technology. +1. Add everything you want to produce or research to the _Available Products_ for these factories. +1. Set the _Queue Count_, specifying how many products can be produced in parallel. +1. Set the _Capacity Per Queue_, specifying how many products can be produced one after another. +1. Add your `RTSProductionProgressBarWidgetComponent` (see [User Interface](UserInterface.md)). + +_Note that, technically, producing units does not differ from researching technology. You can create actor blueprints without physical representation for each technology to research, and add them as products. Then, you can check whether any player owns an actor of that technology for checking a tech tree._ + +### Resource Drain + +1. Add an `RTSResourceDrainComponent` for each type of building gatherers may return resources to. +1. Set the resource types to all resources accepted by the drain. diff --git a/Documents/Manual/Cheats.md b/Documents/Manual/Cheats.md new file mode 100644 index 00000000..b5154395 --- /dev/null +++ b/Documents/Manual/Cheats.md @@ -0,0 +1,18 @@ +## Cheats + +The plugin comes with a small set of built-in cheats you can use. Feel free to create your own cheat manager and add additional cheats. + + +1. Create a cheat manager deriving from `RTSCheatManager`. +1. Set the _Resource Types_ of your cheat manager. +1. At your `RTSPlayerController`, set the _Cheat Manager_ to your cheat manager. +1. At _Edit > Project Settings > Engine > Input_, set and remember your _Console Keys_. + +This will unlock the following built-in cheats to use in your console: + +| Cheat | Description | +| --- | --- | +| Boost | Increases construction and production speed. | +| God | Invulnerability cheat. | +| Money | Adds resources. | +| Victory | Defeat all other players. | diff --git a/Documents/Manual/GameModes.md b/Documents/Manual/GameModes.md new file mode 100644 index 00000000..d34b00fe --- /dev/null +++ b/Documents/Manual/GameModes.md @@ -0,0 +1,20 @@ +## Game Modes + +Clearly, even for real-time strategy games, you may want to define multiple game modes (such as classic Skirmish or story-based campaigns). Thus, we won't restrict you too much here, but just want to provide a few basic things that you might find useful. + + +### Initialization + +1. Set the _Initial Actors_ and their locations for your game mode. This will spawn initial units for each player at their player start as soon as the game starts. + + +### Teams + +1. Set _Num Teams_ to the number of teams your game mode supports. + + +### Game Over + +1. Optionally, set the _Defeat Condition Actor Classes_ for your `RTSGameMode`. This will check whether any actors of the specified types exist for a player whenever he or she loses a unit. If no actor of the specified type remains, the player is defeated. + +_In that case, the game mode will raise the OnPlayerDefeated event to be overridden in subclasses (either blueprint or C++). Note that it is up to you to define how defeated players should be handled, and if/when the game is over, e.g. whether you've making a 1v1, FFA or team game._ diff --git a/Documents/Manual/Images/AddResources.png b/Documents/Manual/Images/AddResources.png new file mode 100644 index 00000000..623fd0ab Binary files /dev/null and b/Documents/Manual/Images/AddResources.png differ diff --git a/Documents/Manual/Images/FloatingCombatTextComponentBeginPlay.png b/Documents/Manual/Images/FloatingCombatTextComponentBeginPlay.png new file mode 100644 index 00000000..30b7334a Binary files /dev/null and b/Documents/Manual/Images/FloatingCombatTextComponentBeginPlay.png differ diff --git a/Documents/Manual/Images/FloatingCombatTextComponentOnResourcesReturned.png b/Documents/Manual/Images/FloatingCombatTextComponentOnResourcesReturned.png new file mode 100644 index 00000000..99bd887a Binary files /dev/null and b/Documents/Manual/Images/FloatingCombatTextComponentOnResourcesReturned.png differ diff --git a/Documents/Manual/Images/HUDDrawFloatingCombatText.png b/Documents/Manual/Images/HUDDrawFloatingCombatText.png new file mode 100644 index 00000000..e7826dcf Binary files /dev/null and b/Documents/Manual/Images/HUDDrawFloatingCombatText.png differ diff --git a/Documents/Manual/Images/HUDDrawSelectionFrame.png b/Documents/Manual/Images/HUDDrawSelectionFrame.png new file mode 100644 index 00000000..d244c91e Binary files /dev/null and b/Documents/Manual/Images/HUDDrawSelectionFrame.png differ diff --git a/Documents/Manual/Images/HUDHideSelectionFrame.png b/Documents/Manual/Images/HUDHideSelectionFrame.png new file mode 100644 index 00000000..448005b1 Binary files /dev/null and b/Documents/Manual/Images/HUDHideSelectionFrame.png differ diff --git a/Documents/Manual/Images/HealthBarWidget.png b/Documents/Manual/Images/HealthBarWidget.png new file mode 100644 index 00000000..073d2190 Binary files /dev/null and b/Documents/Manual/Images/HealthBarWidget.png differ diff --git a/Documents/Manual/Images/HoveredActorWidget.png b/Documents/Manual/Images/HoveredActorWidget.png new file mode 100644 index 00000000..772392da Binary files /dev/null and b/Documents/Manual/Images/HoveredActorWidget.png differ diff --git a/Documents/Manual/Images/IssueAttackOrder.png b/Documents/Manual/Images/IssueAttackOrder.png new file mode 100644 index 00000000..960e1789 Binary files /dev/null and b/Documents/Manual/Images/IssueAttackOrder.png differ diff --git a/Documents/Manual/Images/IssueBeginConstructionOrder.png b/Documents/Manual/Images/IssueBeginConstructionOrder.png new file mode 100644 index 00000000..c631a0ca Binary files /dev/null and b/Documents/Manual/Images/IssueBeginConstructionOrder.png differ diff --git a/Documents/Manual/Images/IssueGatherOrder.png b/Documents/Manual/Images/IssueGatherOrder.png new file mode 100644 index 00000000..14c605f4 Binary files /dev/null and b/Documents/Manual/Images/IssueGatherOrder.png differ diff --git a/Documents/Manual/Images/IssueMoveOrder.png b/Documents/Manual/Images/IssueMoveOrder.png new file mode 100644 index 00000000..bfcc034b Binary files /dev/null and b/Documents/Manual/Images/IssueMoveOrder.png differ diff --git a/Documents/Manual/Images/IssueStopOrder.png b/Documents/Manual/Images/IssueStopOrder.png new file mode 100644 index 00000000..4f1fbf7d Binary files /dev/null and b/Documents/Manual/Images/IssueStopOrder.png differ diff --git a/Documents/Manual/Images/LoadActor.png b/Documents/Manual/Images/LoadActor.png new file mode 100644 index 00000000..a1d4dbb2 Binary files /dev/null and b/Documents/Manual/Images/LoadActor.png differ diff --git a/Documents/Manual/Images/OnConstructionFinished.png b/Documents/Manual/Images/OnConstructionFinished.png new file mode 100644 index 00000000..8adf79d5 Binary files /dev/null and b/Documents/Manual/Images/OnConstructionFinished.png differ diff --git a/Documents/Manual/Images/OnHealthChanged.png b/Documents/Manual/Images/OnHealthChanged.png new file mode 100644 index 00000000..0bf33a32 Binary files /dev/null and b/Documents/Manual/Images/OnHealthChanged.png differ diff --git a/Documents/Manual/Images/OnKilled.png b/Documents/Manual/Images/OnKilled.png new file mode 100644 index 00000000..97d85936 Binary files /dev/null and b/Documents/Manual/Images/OnKilled.png differ diff --git a/Documents/Manual/Images/OnOrderChanged.png b/Documents/Manual/Images/OnOrderChanged.png new file mode 100644 index 00000000..2c5a9c03 Binary files /dev/null and b/Documents/Manual/Images/OnOrderChanged.png differ diff --git a/Documents/Manual/Images/OnProductionFinished.png b/Documents/Manual/Images/OnProductionFinished.png new file mode 100644 index 00000000..95d0934f Binary files /dev/null and b/Documents/Manual/Images/OnProductionFinished.png differ diff --git a/Documents/Manual/Images/OnResourcesChanged.png b/Documents/Manual/Images/OnResourcesChanged.png new file mode 100644 index 00000000..8dce2893 Binary files /dev/null and b/Documents/Manual/Images/OnResourcesChanged.png differ diff --git a/Documents/Manual/Images/SelectionFrameWidget.png b/Documents/Manual/Images/SelectionFrameWidget.png new file mode 100644 index 00000000..0716b6d8 Binary files /dev/null and b/Documents/Manual/Images/SelectionFrameWidget.png differ diff --git a/Documents/Manual/Images/TransferOwnership.png b/Documents/Manual/Images/TransferOwnership.png new file mode 100644 index 00000000..234e37a7 Binary files /dev/null and b/Documents/Manual/Images/TransferOwnership.png differ diff --git a/Documents/Manual/Images/UIHideSelectionFrame.png b/Documents/Manual/Images/UIHideSelectionFrame.png new file mode 100644 index 00000000..b6edff10 Binary files /dev/null and b/Documents/Manual/Images/UIHideSelectionFrame.png differ diff --git a/Documents/Manual/Images/UIShowSelectionFrame.png b/Documents/Manual/Images/UIShowSelectionFrame.png new file mode 100644 index 00000000..feb31a4f Binary files /dev/null and b/Documents/Manual/Images/UIShowSelectionFrame.png differ diff --git a/Documents/Manual/Images/UnloadActor.png b/Documents/Manual/Images/UnloadActor.png new file mode 100644 index 00000000..0f901995 Binary files /dev/null and b/Documents/Manual/Images/UnloadActor.png differ diff --git a/Documents/Manual/Images/UpdateHealthBarPositionAndSize.png b/Documents/Manual/Images/UpdateHealthBarPositionAndSize.png new file mode 100644 index 00000000..887cf6a1 Binary files /dev/null and b/Documents/Manual/Images/UpdateHealthBarPositionAndSize.png differ diff --git a/Documents/Manual/Images/UpdateHealthBarValue.png b/Documents/Manual/Images/UpdateHealthBarValue.png new file mode 100644 index 00000000..074cef80 Binary files /dev/null and b/Documents/Manual/Images/UpdateHealthBarValue.png differ diff --git a/Documents/Manual/Images/UpdateHoveredActorData.png b/Documents/Manual/Images/UpdateHoveredActorData.png new file mode 100644 index 00000000..70805917 Binary files /dev/null and b/Documents/Manual/Images/UpdateHoveredActorData.png differ diff --git a/Documents/Manual/Images/UpdateHoveredActorPositionAndSize.png b/Documents/Manual/Images/UpdateHoveredActorPositionAndSize.png new file mode 100644 index 00000000..278313f7 Binary files /dev/null and b/Documents/Manual/Images/UpdateHoveredActorPositionAndSize.png differ diff --git a/Documents/Manual/Manual.md b/Documents/Manual/Manual.md new file mode 100644 index 00000000..45c4f08c --- /dev/null +++ b/Documents/Manual/Manual.md @@ -0,0 +1,14 @@ +# Manual + +## Contents + +1. [Setup](Setup.md) +1. [Game Modes](GameModes.md) +1. [Maps](Maps.md) +1. [Units](Units.md) +1. [Buildings](Buildings.md) +1. [Resources](Resources.md) +1. [User Interface](UserInterface.md) +1. [AI Players](AIPlayers.md) +1. [Scripting](Scripting.md) +1. [Cheats](Cheats.md) diff --git a/Documents/Manual/Maps.md b/Documents/Manual/Maps.md new file mode 100644 index 00000000..87525be6 --- /dev/null +++ b/Documents/Manual/Maps.md @@ -0,0 +1,46 @@ +## Creating Maps + +For the plugin, you'll design and create your maps the same way you're used to when using Unreal Engine, for the most part. This section serves as a short checklist for you, and highlights some setup that is supposed to make it easier for you to get started. Some steps are mandatory for some features of the plugin to work, however, such as vision. + + +### Game Mode & Geometry + +1. Use your game mode in the world settings. +1. Create your level geometry and lighting as usual. + +### Camera + +1. Add an `RTSCameraBoundsVolume` to the map. +1. Use the _Brush Settings_ to adjust the camera bounds as desired. + +### Navigation + +1. Add a `NavMeshBoundsVolume` to the map. +1. Use the _Brush Settings_ to adjust have the nav mesh bounds encompass your whole level. +1. Build navigation. You may press P to review your results in the viewport. + +### Player Starts + +1. Add `RTSPlayerStart`s to the map. +1. Set the _Team Index_ for each player start. + +### Minimap + +1. Add an `RTSMinimapVolume` to the very center of your map. +1. Set its brush size to match the extents of your playable map. +1. Set the _Minimap Image_ to a nice top-down screenshot of your map. + +### Fog Of War + +1. Add an `RTSVisionVolume` to the very center of your map, encompassing the whole valid visible map area. +1. Set the _Size In Tiles_ of the vision volume to match your minimap background images (e.g. 256). +1. Add a `PostProcessVolume` to your map, and check _Infinite Extent (Unbound)_. +1. Add an `RTSFogOfWarActor` to your map. +1. Set the _Fog Of War Volume_ reference to the post process volume created before. +1. Set the _Fog Of War Material_ of the actor (e.g. to the `M_RTSFogOfWar` material shipped with the plugin). + +### Pre-Placed Units + +1. Add any actors that should initially on the battlefield. +1. For each of these actors, at the `RTSOwnerComponent`, set the _Initial Owner Player Index_ to specify which player should own them. +1. When pre-placing buildings, at the `RTSConstructionSiteComponent`, set their _State_ to _Finished_ if they should be ready from the beginning. diff --git a/Documents/Manual/Resources.md b/Documents/Manual/Resources.md new file mode 100644 index 00000000..d5c808e6 --- /dev/null +++ b/Documents/Manual/Resources.md @@ -0,0 +1,9 @@ +## Creating Resource Sources + +In case you missed that step earlier, make your to set up your resource types as explained in [Setup](Setup.md). Then, create resource sources as follows: + +1. See [Creating Units](Units.md) (Appearance only; can be a standard actor). +1. Add an `RTSResourceSourceComponent`. +1. Set the resource type and maximum and current resources of the source. +1. Set the gathering factor for increasing the yield of any gatherers (e.g. golden minerals in StarCraft). +1. If you want gatherers to enter the resource source (e.g. Refinery in StarCraft), check _Gatherer Must Enter_, set the _Gatherer Capacity_, and add an `RTSContainerComponent`. diff --git a/Documents/Manual/Scripting.md b/Documents/Manual/Scripting.md new file mode 100644 index 00000000..c0e7c88f --- /dev/null +++ b/Documents/Manual/Scripting.md @@ -0,0 +1,79 @@ +## Scripting + +Occasionally, you want to create additional gameplay (especially when creating a story campaign). This section highlights additional functions you can call from blueprints, as well as events you may handle. + +Feel free to explore the plugin yourself by looking at what other functions and events each component provides, and open an issue if you're missing something. + + +### RTSContainerComponent +#### Functions + +| Node | Description | +| --- | --- | +| ![Load Actor](Images/LoadActor.png) | Adds the specified actor to this container. | +| ![Unload Actor](Images/UnloadActor.png) | Removes the specified actor from this container. | + + +### RTSConstructionSiteComponent +#### Events + +| Event | Description | +| --- | --- | +| ![On Construction Finished](Images/OnConstructionFinished.png) | Event when the construction timer has expired. | + + +### RTSGameMode +#### Functions + +| Node | Description | +| --- | --- | +| ![Transfer Ownership](Images/TransferOwnership.png) | Sets the specified player as the owner of the passed actor. | + + +### RTSHealthComponent +#### Events + +| Event | Description | +| --- | --- | +| ![On Health Changed](Images/OnHealthChanged.png) | Event when the current health of the actor has changed. | +| ![On Killed](Images/OnKilled.png) | Event when the actor has been killed. | + + +### RTSPawnAIController +#### Functions + +| Node | Description | +| --- | --- | +| ![Issue Attack Order](Images/IssueAttackOrder.png) | Makes the pawn attack the specified target. | +| ![Issue Begin Construction Order](Images/IssueBeginConstructionOrder.png) | Makes the pawn construct the specified building at the passed location. | +| ![Issue Gather Order](Images/IssueGatherOrder.png) | Makes the pawn gather resources from the specified source. | +| ![Issue Move Order](Images/IssueMoveOrder.png) | Makes the pawn move towards the specified location. | +| ![Issue Stop Order](Images/IssueStopOrder.png) | Makes the pawn stop all actions immediately. | + +#### Events + +| Event | Description | +| --- | --- | +| ![On Order Changed](Images/OnOrderChanged.png) | Event when the pawn has received a new order. | + + +### RTSPlayerResourcesComponent +#### Functions + +| Node | Description | +| --- | --- | +| ![Add Resources](Images/AddResources.png) | Adds the specified resources to the stock of this player. | + +#### Events + +| Event | Description | +| --- | --- | +| ![On Resources Changed](Images/OnResourcesChanged.png) | Event when the current resource stock amount for the player has changed. | + + +### RTSProductionComponent +#### Events + +| Event | Description | +| --- | --- | +| ![On Production Finished](Images/OnProductionFinished.png) | Event when the production timer has expired. | diff --git a/Documents/Manual/Setup.md b/Documents/Manual/Setup.md new file mode 100644 index 00000000..1f0c6d7a --- /dev/null +++ b/Documents/Manual/Setup.md @@ -0,0 +1,91 @@ +## Setup + +### Game Framework Setup + +_Real-Time Strategy Plugin for Unreal Engine 4_ extends the [game framework](https://docs.unrealengine.com/en-US/Gameplay/Framework/index.html) provided by Unreal Engine with features that are common in real-time strategy games. + +For this, we embrace the principle of _composition over inheritance_: Most features are implemented by the means of components to add to your actors, to allow for the most flexibility on your side when building your game, especially when it comes to combining the plugin with other third-party software. + +Some things have been carefully added to the existing game framework however, as designed by Epic, and require a few steps to set up. + +The plugin also ships with a few assets that are designed to get you started, e.g. with unit AI, but feel free to modify or replace them as you see fit. + +Make sure _View Plugin Content_ is enabled in your view options. + +1. Create a player controller deriving from `RTSPlayerController`. +1. Create a player state deriving from `RTSPlayerState`. +1. Create a game state deriving from `RTSGameState`. +1. Create a HUD deriving from `RTSHUD`. +1. Create a game mode deriving from `RTSGameMode`. +1. Use your player controller, player state, game state, and HUD in your game mode. +1. Create an AI controller deriving from `RTSPawnAIController`. +1. Set the _Pawn Behavior Tree Asset_ of the new pawn AI controller to `BT_RTSPawnBehaviorTree`. +1. Set the _Pawn Blackboard Asset_ of the new pawn AI controller to `BB_RTSPawnBlackboard`. +1. Create a player start deriving from `RTSPlayerStart`. +1. Create one or more resource types deriving from `RTSResourceType`. +1. Add the resource types to the `PlayerResourcesComponent` of your player controller. + + +### Camera Setup + +Usually, players control a single [pawn](https://docs.unrealengine.com/en-US/Gameplay/Framework/Pawn/index.html) in Unreal Engine. However, in the case of real-time strategy games, players control many units from a camera perspective far out. Thus, the plugin works best when using a simple pawn with a normal camera whose location reflects what the player wants to see right now. Individual units are not directly possessed by the player controllers (or AI controllers), but just "owned" by them. + +#### Creating The Camera + +1. Create a pawn blueprint. +1. Add a `Camera` component. +1. Set the _Location_ of the `Camera` component as desired (e.g. X = 0, Y = 0, Z = 1500). +1. Set the _Rotation_ of the `Camera` component as desired (e.g. X = 0, Y = -75, Z = 0). +1. Use the pawn as _Default Pawn Class_ in your game mode. + +#### Setting Up Camera Movement + +1. Bind the axis `MoveCameraLeftRight` (e.g. to Left and Right keys). +1. Bind the axis `MoveCameraUpDown` (e.g. to Up and Down keys). +1. Bind the axis `ZoomCamera` (e.g. to the mouse wheel axis). +1. At your `RTSPlayerController`, set the _Camera Speed_ (e.g. to 1000). +1. At your `RTSPlayerController`, set the _Camera Scroll Threshold_ (e.g. to 20). +1. At your `RTSPlayerController`, set _Camera Zoom Speed_, _Min Camera Distance_ and _Max Camera Distance_ as desired. + + +### Input Setup + +Many default [input](https://docs.unrealengine.com/en-US/Gameplay/Input/index.html) actions for real-time strategy games are already provided by the plugin. Given that you use an RTSPlayerController in your game mode, you can bind the following actions which should speak for themselves. Clearly, all of these bindings are optional. + +At _Edit > Project Settings > Engine > Input_ ... + +#### Selection + +1. Bind the action `Select` (e.g. to left mouse button). +1. Bind the actions `SaveControlGroup0` to `SaveControlGroup9` (e.g. to CTRL+0 to CTRL+9). +1. Bind the actions `LoadControlGroup0` to `LoadControlGroup9` (e.g. to 0 to 9). +1. Bind the action `AddSelection` (e.g. to Left Shift). +1. Bind the action `ToggleSelection` (e.g. to Left Ctrl). + +#### Orders + +1. Bind the action `IssueOrder` (e.g. to the right mouse button). This will enable typical smart orders, such as moving when right-clicking ground, and attacking when right-clicking enemies. +1. Bind the action `IssueStopOrder` (e.g. to the S key). + +#### Health Bars + +1. Bind the action `ShowHealthBars` (e.g. to the LeftAlt key). + +### Production + +1. Bind the action `CancelProduction` (e.g. to Escape). +1. Bind the action `ShowProductionProgressBars` (e.g. to the LeftAlt key). + +#### Construction + +1. Bind the action `ConfirmBuildingPlacement` (e.g. to Left Mouse Button). +1. Bind the action `CancelBuildingPlacement` (e.g. to Right Mouse Button). +1. Bind the action `CancelConstruction` (e.g. to Escape). +1. Bind the action `ShowConstructionProgressBars` (e.g. to the LeftAlt key). + + +### Gameplay Tags + +The plugin makes use of [gameplay tags](https://docs.unrealengine.com/en-US/Gameplay/Tags/index.html) for enabling condition-based gameplay (such as whether a unit can be attacked or not). + +At _Edit > Project Settings > Project > Gameplay Tags_, add `DT_RTSGameplayTags` to the _Gameplay Tag Table List_. diff --git a/Documents/Manual/Units.md b/Documents/Manual/Units.md new file mode 100644 index 00000000..ce91e447 --- /dev/null +++ b/Documents/Manual/Units.md @@ -0,0 +1,89 @@ +## Creating Units + +As mentioned before, most features of the plugin are implemented by the means of components to add to your actors. Thus, for adding new units (or buildings), you'll usually create a pawn or character, and add various components, depending on the capabitilies you want the unit to have. This approach enables you to combine features just as you like, for instance having buildings that can attack or units that resources can be returned to. + + +### Appearance + +1. Create a new pawn or character blueprint. +1. Check the _Replicates_ flag. +1. Add a static or skeletal mesh and setup its location, rotation and scale as usual. +1. Setup collision (e.g. Capsule Collision) as usual. You may want to disable the collision of your mesh and rely on its capsule instead. +1. Setup your animations. (If you're new to the Unreal animation system, we can readily recommend the tutorial at https://docs.unrealengine.com/latest/INT/Programming/Tutorials/FirstPersonShooter/4/index.html) +1. Add an `RTSNameComponent` and set its localized name if you want to show it in any kind of ingame UI. +1. Add an `RTSDescriptionComponent` and set its localized text if you want to show it in any kind of ingame UI. +1. Add an `RTSPortraitComponent` and set its portrait if you want to show it in any kind of ingame UI. +1. Add an `RTSSelectableComponent`, and set its selection circle material (e.g. to `M_RTSSelectionCircle`) and selection sound. +1. Add an `RTSOwnerComponent`. This will be used to store (and replicate) the owner of the unit for all players (e.g. for showing team colors). +1. Add your `RTSHoveredActorWidgetComponent` (see [User Interface](UserInterface.md)). + + +### Movement + +1. Add a movement component (e.g. `FloatingPawnMovement` or `CharacterMovement`) and set up its speed properties as usual. The plugin also ships with a `RTSPawnMovementComponent` that basically just adds rotation updates to the built-in `FloatingPawnMovement`. + + +### Vision + +1. Add the `RTSVision` component to your units and set their _Sight Radius_ (e.g. 1000). + + +### Combat + +#### AI + +As mentioned before, units are not directly possessed by player controllers in the plugin. Instead, every unit is possessed by an AIController that will carry out orders issued by the players. + +1. Set the pawn AI controller class to your `RTSPawnAIController`. +1. Ensure _Pawn > Auto Possess AI_ is set to _Placed in World or Spawned_. + +#### Health & Damage + +1. Add an `RTSAttackableComponent` and `RTSGameplayTagsComponent` to any actors that can be attacked. +1. Add the `Status.Permanent.CanBeAttacked` tag to the `RTSGameplayTagsComponent`. +1. Set the _Maximum Health_ of the `RTSHealthComponent`. +1. Add your `RTSHealthBarWidgetComponent` (see [User Interface](UserInterface.md)). + +1. Add the `RTSAttackComponent` to any actors than can attack. +1. Add an attack to the `RTSAttackComponent` of these actors, setting its _Cooldown, Damage, Range, Acquisition Radius_ and _Chase Radius_. + +_Setting the Damage Type is optional._ + +#### Projectiles + +If you don't specify a projectile, the damage will be applied immediately. In order to setup a projectile for the unit: + +1. Create an actor deriving from `RTSProjectile`. +1. Add a static mesh and any visual effects. +1. At the `ProjectileMovement` component, set its _Initial Speed_ (e.g. to 1000). +1. At the `RTSAttackComponent`, reference the new projectile in your attack. + + +### Production Costs + +1. Add the `RTSProductionCostComponent` to everything you want to be produced. +1. Set the _Production Time_ for these products. +1. Set the _Resources_ to any resources required for production. +1. Set the _Production Cost Type_ to to _Pay Immediately_ if all costs should be paid in full when starting production, or to _Pay Over Time_ for continuously paying costs (similar to Command & Conquer). +1. Set the _Refund Factor_ to the factor to multiply refunded resources with after cancelation. +1. Add the `RTSRequirementsComponent` if the actor should have any requirements, and set the _Required Actors_. + + +### Construction + +1. Add an `RTSBuilderComponent` to any actors you want to be able to construct buildings. +1. Set the _Constructible Building Classes_ for these builders. +1. Check _Enter Construction Site_ if you want the builder to be unavailable while building (similar to Orcs in WarCraft). + + +### Gathering + +1. Add an `RTSGathererComponent` to any actor that should be able to gather resources. +1. Add any resource type the gatherer should be able to gather to _Gathered Resources_. + 1. Gathering works similar to attacks, with "damage" and "cooldown". Set _Amount Per Gathering_ to the value to add to the gatherers inventory each time the cooldown is finished. + 1. Set the _Cooldown_ to the time between two gatherings. + 1. Set the _Capacity_ to the amount of resources the gatherer can carry before returning to a resource drain. + 1. Check _Needs Return To Drain_ if the gatherer needs to move to another actor for returning resources (e.g. Age of Empires). Uncheck if they should return all gathered resources immediately when hitting the capacity limit (e.g. Undead in WarCraft). + 1. Set _Range_ as desired. +1. Add all _Resource Source Actor Classes_ the gatherer may gather from (e.g. Undead in Warcraft need Haunted Gold Mine). +1. Set the _Resource Sweep Radius_ to the radius in which the gatherer should look for similar resources if their current source is depleted. diff --git a/Documents/Manual/UserInterface.md b/Documents/Manual/UserInterface.md new file mode 100644 index 00000000..8bc54c4b --- /dev/null +++ b/Documents/Manual/UserInterface.md @@ -0,0 +1,146 @@ +## Creating The User Interface + +Usually, you'll want to create a very individual user interface for your own game. However, some things are very common to real-time strategy games, such as health bars or minimaps, and we want to provide you a small head start at least, mostly by the means of events you can implement. As always, feel free to create your own UI widgets as you see fit - you should be able to apply them easily with the plugin. + + +### Selection Frames + +In your HUD, implement the `DrawSelectionFrame` and `HideSelectionFrame` events as desired. + +Example: + +1. Create a widget for drawing the selection frame. + +![Selection Frame Widget](Images/SelectionFrameWidget.png) + +2. Add your widget to any kind of user interface your player controller knows about. +3. In that user interface, provide a function for showing the selection frame. + +![UI - Show Selection Frame](Images/UIShowSelectionFrame.png) + +4. In the user interface, provide a function for hiding the selection frame. + +![UI - Hide Selection Frame](Images/UIHideSelectionFrame.png) + +5. In your HUD, forward the `DrawSelectionFrame` event to your UI. + +![HUD - Show Selection Frame](Images/HUDDrawSelectionFrame.png) + +6. In the HUD, forward the `HideSelectionFrame` event to your UI. + +![HUD - Hide Selection Frame](Images/HUDHideSelectionFrame.png) + + +### Selected Unit Status + +1. Create a new widget blueprint. +1. Create the widget where appropriate (e.g. `BeginPlay` of your player controller) and add it to your viewport. +1. Listen to the `OnSelectionChanged` event broadcasted by the `RTSPlayerController` and update your UI. + + +### Health Bars + +1. In your HUD, set _Always Show Health Bars, Show Hover Health Bars, Show Selection Health Bars_ and _Show Hotkey Health Bars_ as desired. +2. Create a widget for drawing the health bar. + +![Health Bar Widget](Images/HealthBarWidget.png) + +_You might want to make sure that the visibility of the widget is set to Hit Test Invisible. Otherwise, it will block mouse input from your player._ + +3. Create a component deriving from `RTSHealthBarWidgetComponent`, and set its _Widget Class_ to your health bar widget. +4. Forward the `UpdateHealthBar` event to your health bar widget. + +![Component - Update Health Bar Value](Images/UpdateHealthBarValue.png) + +5. Forward the `UpdatePositionAndSize` event to your health bar widget. + +![Component - Update Health Bar Position And Size](Images/UpdateHealthBarPositionAndSize.png) + + +### Hovered Actors + +1. Create a widget for drawing name plates (or whatever other information you'd like to display for hovered actors). + +![Hovered Actor Widget](Images/HoveredActorWidget.png) + +_You might want to make sure that the visibility of the widget is set to Hit Test Invisible. Otherwise, it will block mouse input from your player._ + +2. Create a component deriving from `RTSHoveredActorWidgetComponent`, and set its Widget Class to your new widget widget. +3. Forward the `UpdateData` event to your widget. + +![Component - Update Data](Images/UpdateHoveredActorData.png) + +4. Forward the `UpdatePositionAndSize` event to your widget. + +![Component - Update Widget Position And Size](Images/UpdateHoveredActorPositionAndSize.png) + + +### Building Cursors + +1. Create an actor deriving from `RTSBuildingCursor` (or use the `BP_RTSBuildingCursor` shipped with the plugin). +1. In your player controller, set the building cursor reference. + + +### Production UI + +1. Use `GetAvailableProducts` of a selected production actor to create buttons for your production options (e.g. whenever the player controller raises OnSelectionChanged). +1. Call `IssueProductionOrder` of your player controller whenever one of these buttons is clicked. + + +### Production Progress Bars + +1. In your HUD, set _Always Show Production Progress Bars, Show Hover Production Progress Bars, Show Selection Production Progress Bars_ and _Show Hotkey Production Progress Bars_ as desired. +2. Create a widget for drawing the production progress bar. + +_See the [Health Bars](#health-bars) section for an example._ + +3. Create a component deriving from `RTSProductionProgressBarWidgetComponent`, and set its _Widget Class_ to your progress bar widget. +4. Forward the `UpdateProductionProgressBar` event to your progress bar widget. +5. Forward the `UpdatePositionAndSize` event to your progress bar widget. + + +### Construction UI + +1. Use `GetConstructibleBuildingClasses` of a selected builder to create buttons for your construction options (e.g. whenever the player controller raises `OnSelectionChanged`). +1. Call `BeginBuildingPlacement` of your player controller whenever one of these buttons is clicked. + + +### Construction Progress Bars + +1. In your HUD, set _Always Show Construction Progress Bars, Show Hover Construction Progress Bars, Show Selection Construction Progress Bars_ and _Show Hotkey Construction Progress Bars_ as desired. +2. Create a widget for drawing the construction progress bar. + +_See the [Health Bars](#health-bars) section for an example._ + +3. Create a component deriving from `RTSConstructionProgressBarWidgetComponent`, and set its _Widget Class_ to your progress bar widget. +4. Forward the `UpdateConstrutionProgressBar` event to your progress bar widget. +5. Forward the `UpdatePositionAndSize` event to your progress bar widget. + + +### Resources UI + +1. Create a widget for showing your current resources. +1. Handle the `OnResourcesChanged` event raised by the `PlayerResourcesComponent` attached to your player controller to update your UI. + + +### Minimap + +1. Add the `WBP_RTSMinimapWidget` to your UI, with a size matching your minimap volume images (e.g. 256 x 256). +1. Set the _Draw Background, Draw Units With Team Colors, Draw Vision_ and _Draw View Frustum_ flags as desired. +1. If you checked _Draw Units With Team Colors_, set the _Own Units Brush, Enemy Units Brush_ and _Neutral Units Brush_ as desired. + + +### Floating Combat Texts + +1. In your HUD, enable _Show Floating Combat Texts_. +2. Set _Floating Combat Text Speed_ and _Fade Out Floating Combat Texts_ as desired. +3. Add a `RTSFloatingCombatTextComponent` to any actor that should be able to display texts above them. +4. Create an actor component for adding the actual floating combat texts. + +![Floating Combat Text Component - BeginPlay](Images/FloatingCombatTextComponentBeginPlay.png) +![Floating Combat Text Component - OnResourcesReturned](Images/FloatingCombatTextComponentOnResourcesReturned.png) + +5. Add your actor component to all actors that should be able to add floating combat texts. +6. In your HUD, handle the `DrawFloatingCombatText` event. + +![HUD - Draw Floating Combat Text](Images/HUDDrawFloatingCombatText.png) diff --git a/README.md b/README.md index 8ffb2dd1..7fc7ff37 100644 --- a/README.md +++ b/README.md @@ -1 +1,102 @@ -# ue4-rts \ No newline at end of file +[![license](https://img.shields.io/github/license/npruehs/ue4-rts.svg?maxAge=2592000)](https://github.com/npruehs/ue4-rts/blob/develop/LICENSE) + +# Real-Time Strategy Plugin for Unreal Engine 4 + +Open source Real-Time Strategy Plugin for Unreal Engine 4 developed by the original creators of [Hostile Worlds](http://www.indiedb.com/games/hostile-worlds/) for Unreal Engine 3. + +We really love the spirit of Unreal 4 moving to open-source, and we'd love to give something back. + +Note that we're still in heavy development. Some things are already in place, such as + +* Camera Movement & Bounds +* Initial Unit Placement +* Single- and Multi-Unit Selection +* Movement Orders +* Stop Orders +* Attack Orders +* Health, Damage (and Healing), Cooldown, Range +* Victory Conditions +* Projectiles +* Unit Target Acquisition +* Control Groups +* Health Bars +* Minimap +* Teams +* Constructions +* Unit Production/Tech Research +* Resource Gathering +* Fog of War +* AI Players +* Cheats + +All of this is already completely working in multiplayer as well, and has been fully exposed to scripting, enabling you to update UI and play animations and sounds. + +We're going to add all of this to the Unreal Marketplace for free as well soon (tm). + +For a quick look, just open `Source/RTSProject/RTSProject.uproject`, load `Maps/SKM-DarkSpace.umap` in the editor and hit Play. We recommend taking a closer look at that map (and the rest of the example project) as reference for your own one as well. + + +## Setup + +### Prerequisites + +Real-Time Strategy Plugin for Unreal Engine 4 currently supports the following Unreal Engine Versions: + +* 4.24 +* 4.25 + +### Adding The Plugin (Blueprint Project) + +1. Close your Unreal Editor. +1. [Download](https://github.com/npruehs/ue4-rts/releases) the latest release. +1. Copy the `RealTimeStrategy` folder to `Plugins` folder next to your `.uproject` file (create if necessary). +1. Start the Unreal Editor. + +### Adding The Plugin (C++ Project) + +1. Close your Unreal Editor. +1. [Clone](https://github.com/npruehs/ue4-rts) the repository or [download](https://github.com/npruehs/ue4-rts/releases) a release. +1. Copy the `RealTimeStrategy` folder to `Plugins` folder next to your `.uproject` file (create if necessary). +1. Right-click your .uproject file and select Re-generate Visual Studio project files. +1. Build the resulting solution in Visual Studio. +1. Start the Unreal Editor. + + +## Getting Started + +Take a look at the [official manual](Documents/Manual/Manual.md) to get started right away! + + +## Bugs & Feature Requests + +We are sorry that you've experienced issues or are missing a feature! After verifying that you are using the latest version and having checked whether a [similar issue](https://github.com/npruehs/ue4-rts/issues) has already been reported, feel free to [open a new issue](https://github.com/npruehs/ue4-rts/issues/new). In order to help us resolving your problem as fast as possible, please include the following details in your report: + +* Steps to reproduce +* What happened? +* What did you expect to happen? + +After being able to reproduce the issue, we'll look into fixing it immediately. + + +## Development Cycle + +We know that using this plugin in production requires you to be completely sure about stability and compatibility. Thus, new releases are created using [Semantic Versioning](http://semver.org/). In short: + +* Version numbers are specified as MAJOR.MINOR.PATCH. +* MAJOR version increases indicate incompatible API changes. +* MINOR version increases indicate added functionality in a backwards compatible manner. +* PATCH version increases indicate backwards compatible bug fixes. + +You'll always find all available releases and their respective release notes at: + +https://github.com/npruehs/ue4-rts/releases + + +## Contributing + +You want to contribute to Real-Time Strategy Plugin for Unreal Engine 4? Great! Take a look at [Contributing](CONTRIBUTING.md) to get started right away! + + +## License + +Real-Time Strategy Plugin for Unreal Engine 4 is released under the [MIT License](https://github.com/npruehs/ue4-rts/blob/develop/LICENSE). diff --git a/Source/RTSProject/Config/DefaultEditor.ini b/Source/RTSProject/Config/DefaultEditor.ini new file mode 100644 index 00000000..e69de29b diff --git a/Source/RTSProject/Config/DefaultEngine.ini b/Source/RTSProject/Config/DefaultEngine.ini new file mode 100644 index 00000000..bff9002b --- /dev/null +++ b/Source/RTSProject/Config/DefaultEngine.ini @@ -0,0 +1,164 @@ +[URL] +[/Script/Engine.RendererSettings] +r.MobileHDR=False +r.Mobile.DisableVertexFog=True +r.Shadow.CSM.MaxMobileCascades=2 +r.MobileMSAA=1 +r.Mobile.UseLegacyShadingModel=False +r.Mobile.AllowDitheredLODTransition=False +r.Mobile.AllowSoftwareOcclusion=False +r.DiscardUnusedQuality=False +r.AllowOcclusionQueries=True +r.MinScreenRadiusForLights=0.030000 +r.MinScreenRadiusForDepthPrepass=0.030000 +r.MinScreenRadiusForCSMDepth=0.010000 +r.PrecomputedVisibilityWarning=False +r.TextureStreaming=True +Compat.UseDXT5NormalMaps=False +r.ClearCoatNormal=False +r.ReflectionCaptureResolution=128 +r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True +r.ForwardShading=False +r.VertexFoggingForOpaque=True +r.AllowStaticLighting=True +r.NormalMapsForStaticLighting=False +r.GenerateMeshDistanceFields=False +r.DistanceFieldBuild.EightBit=False +r.GenerateLandscapeGIData=False +r.DistanceFieldBuild.Compress=False +r.TessellationAdaptivePixelsPerTriangle=48.000000 +r.SeparateTranslucency=False +r.TranslucentSortPolicy=0 +TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) +r.CustomDepth=1 +r.CustomDepthTemporalAAJitter=True +r.PostProcessing.PropagateAlpha=0 +r.DOF.Algorithm=True +r.DefaultFeature.Bloom=False +r.DefaultFeature.AmbientOcclusion=False +r.DefaultFeature.AmbientOcclusionStaticFraction=True +r.DefaultFeature.AutoExposure=False +r.DefaultFeature.AutoExposure.Method=0 +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=False +r.UsePreExposure=False +r.DefaultFeature.MotionBlur=False +r.DefaultFeature.LensFlare=False +r.TemporalAA.Upsampling=False +r.DefaultFeature.AntiAliasing=0 +r.DefaultFeature.LightUnits=1 +r.DefaultBackBufferPixelFormat=4 +r.Shadow.UnbuiltPreviewInGame=True +r.StencilForLODDither=False +r.EarlyZPass=3 +r.EarlyZPassMovable=True +r.EarlyZPassOnlyMaterialMasking=False +r.DBuffer=True +r.ClearSceneMethod=1 +r.BasePassOutputsVelocity=False +r.SelectiveBasePassOutputs=False +bDefaultParticleCutouts=False +fx.GPUSimulationTextureSizeX=1024 +fx.GPUSimulationTextureSizeY=1024 +r.AllowGlobalClipPlane=False +r.GBufferFormat=1 +r.MorphTarget.Mode=True +r.GPUCrashDebugging=False +vr.InstancedStereo=False +vr.MultiView=False +vr.MobileMultiView=False +vr.MobileMultiView.Direct=False +vr.MonoscopicFarField=False +vr.RoundRobinOcclusion=False +vr.ODSCapture=False +r.WireframeCullThreshold=5.000000 +r.SupportStationarySkylight=True +r.SupportLowQualityLightmaps=True +r.SupportPointLightWholeSceneShadows=True +r.SupportAtmosphericFog=True +r.SkinCache.CompileShaders=False +r.Mobile.EnableStaticAndCSMShadowReceivers=True +r.Mobile.EnableMovableLightCSMShaderCulling=True +r.Mobile.AllowDistanceFieldShadows=True +r.Mobile.AllowMovableDirectionalLights=True +r.MobileNumDynamicPointLights=4 +r.MobileDynamicPointLightsUseStaticBranch=True +r.SkinCache.SceneMemoryLimitInMB=128.000000 +r.GPUSkin.Limit2BoneInfluences=False +r.SupportDepthOnlyIndexBuffers=True +r.SupportReversedIndexBuffers=True +r.SupportMaterialLayers=False + +[/Script/EngineSettings.GameMapsSettings] +EditorStartupMap=/Engine/Maps/Templates/Template_Default.Template_Default +LocalMapOptions= +TransitionMap=None +bUseSplitscreen=False +TwoPlayerSplitscreenLayout=Horizontal +ThreePlayerSplitscreenLayout=FavorTop +FourPlayerSplitscreenLayout=Grid +bOffsetPlayerGamepadIds=False +GameInstanceClass=/Script/Engine.GameInstance +GameDefaultMap=/Engine/Maps/Entry.Entry +ServerDefaultMap=/Engine/Maps/Entry.Entry +GlobalDefaultGameMode=/Script/Engine.GameModeBase +GlobalDefaultServerGameMode=None + +[/Script/Slate.SlateSettings] +bExplicitCanvasChildZOrder=True + +[/Script/IOSRuntimeSettings.IOSRuntimeSettings] +bSupportsPortraitOrientation=False +bSupportsUpsideDownOrientation=False +bSupportsLandscapeLeftOrientation=True +PreferredLandscapeOrientation=LandscapeLeft + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Mobile +AppliedTargetedHardwareClass=Mobile +DefaultGraphicsPerformance=Scalable +AppliedDefaultGraphicsPerformance=Scalable + +[/Script/Engine.PhysicsSettings] +DefaultGravityZ=-980.000000 +DefaultTerminalVelocity=4000.000000 +DefaultFluidFriction=0.300000 +SimulateScratchMemorySize=262144 +RagdollAggregateThreshold=4 +TriangleMeshTriangleMinAreaThreshold=5.000000 +bEnableAsyncScene=False +bEnableShapeSharing=False +bEnablePCM=True +bEnableStabilization=False +bWarnMissingLocks=True +bEnable2DPhysics=False +PhysicErrorCorrection=(PingExtrapolation=0.100000,PingLimit=100.000000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=1.000000,MaxRestoredStateError=1.000000,MaxLinearHardSnapDistance=400.000000,PositionLerp=0.000000,AngleLerp=0.400000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=10.000000,ErrorAccumulationSeconds=0.500000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) +LockedAxis=Invalid +DefaultDegreesOfFreedom=Full3D +BounceThresholdVelocity=200.000000 +FrictionCombineMode=Average +RestitutionCombineMode=Average +MaxAngularVelocity=3600.000000 +MaxDepenetrationVelocity=0.000000 +ContactOffsetMultiplier=0.020000 +MinContactOffset=2.000000 +MaxContactOffset=8.000000 +bSimulateSkeletalMeshOnDedicatedServer=True +DefaultShapeComplexity=CTF_UseSimpleAndComplex +bDefaultHasComplexCollision=True +bSuppressFaceRemapTable=False +bSupportUVFromHitResults=False +bDisableActiveActors=False +bDisableKinematicStaticPairs=False +bDisableKinematicKinematicPairs=False +bDisableCCD=False +bEnableEnhancedDeterminism=False +MaxPhysicsDeltaTime=0.033333 +bSubstepping=False +bSubsteppingAsync=False +MaxSubstepDeltaTime=0.016667 +MaxSubsteps=6 +SyncSceneSmoothingFactor=0.000000 +AsyncSceneSmoothingFactor=0.990000 +InitialAverageFrameRate=0.016667 +PhysXTreeRebuildRate=10 +DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2) diff --git a/Source/RTSProject/Config/DefaultGame.ini b/Source/RTSProject/Config/DefaultGame.ini new file mode 100644 index 00000000..6d09887f --- /dev/null +++ b/Source/RTSProject/Config/DefaultGame.ini @@ -0,0 +1,53 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=A1D9F60D450E99E8152DDE8F6BA9465D + +[/Script/UnrealEd.ProjectPackagingSettings] +Build=IfProjectHasCode +BuildConfiguration=PPBC_Development +BuildTarget= +StagingDirectory=(Path="") +FullRebuild=False +ForDistribution=False +IncludeDebugFiles=False +BlueprintNativizationMethod=Disabled +bIncludeNativizedAssetsInProjectGeneration=False +bExcludeMonolithicEngineHeadersInNativizedCode=False +UsePakFile=True +bGenerateChunks=False +bGenerateNoChunks=False +bChunkHardReferencesOnly=False +bForceOneChunkPerFile=False +MaxChunkSize=0 +bBuildHttpChunkInstallData=False +HttpChunkInstallDataDirectory=(Path="") +PakFileCompressionFormats= +PakFileAdditionalCompressionOptions= +HttpChunkInstallDataVersion= +IncludePrerequisites=True +IncludeAppLocalPrerequisites=False +bShareMaterialShaderCode=False +bSharedMaterialNativeLibraries=False +ApplocalPrerequisitesDirectory=(Path="") +IncludeCrashReporter=False +InternationalizationPreset=English ++CulturesToStage=en +bCookAll=False +bCookMapsOnly=False +bCompressed=False +bSkipEditorContent=False +bSkipMovies=False ++IniKeyBlacklist=KeyStorePassword ++IniKeyBlacklist=KeyPassword ++IniKeyBlacklist=rsa.privateexp ++IniKeyBlacklist=rsa.modulus ++IniKeyBlacklist=rsa.publicexp ++IniKeyBlacklist=aes.key ++IniKeyBlacklist=SigningPublicExponent ++IniKeyBlacklist=SigningModulus ++IniKeyBlacklist=SigningPrivateExponent ++IniKeyBlacklist=EncryptionKey ++IniKeyBlacklist=IniKeyBlacklist ++IniKeyBlacklist=IniSectionBlacklist ++MapsToCook=(FilePath="/Game/Maps/SKM-DarkSpace") + + diff --git a/Source/RTSProject/Config/DefaultGameplayTags.ini b/Source/RTSProject/Config/DefaultGameplayTags.ini new file mode 100644 index 00000000..aaa73d48 --- /dev/null +++ b/Source/RTSProject/Config/DefaultGameplayTags.ini @@ -0,0 +1,9 @@ + +[/Script/GameplayTags.GameplayTagsSettings] +ImportTagsFromConfig=True +WarnOnInvalidTags=True +FastReplication=True ++GameplayTagTableList=/RealTimeStrategy/Data/DT_RTSGameplayTags.DT_RTSGameplayTags +NumBitsForContainerSize=6 +NetIndexFirstBitSegment=16 + diff --git a/Source/RTSProject/Config/DefaultInput.ini b/Source/RTSProject/Config/DefaultInput.ini new file mode 100644 index 00000000..43f4ea47 --- /dev/null +++ b/Source/RTSProject/Config/DefaultInput.ini @@ -0,0 +1,88 @@ + +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Z",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Z",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bDefaultViewportMouseLock=False +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 ++ActionMappings=(ActionName="Select",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) ++ActionMappings=(ActionName="IssueOrder",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) ++ActionMappings=(ActionName="IssueStopOrder",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=S) ++ActionMappings=(ActionName="SaveControlGroup0",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Zero) ++ActionMappings=(ActionName="SaveControlGroup1",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=One) ++ActionMappings=(ActionName="SaveControlGroup2",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Two) ++ActionMappings=(ActionName="SaveControlGroup3",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Three) ++ActionMappings=(ActionName="SaveControlGroup4",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Four) ++ActionMappings=(ActionName="SaveControlGroup5",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Five) ++ActionMappings=(ActionName="SaveControlGroup6",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Six) ++ActionMappings=(ActionName="SaveControlGroup7",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Seven) ++ActionMappings=(ActionName="SaveControlGroup8",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Eight) ++ActionMappings=(ActionName="SaveControlGroup9",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Nine) ++ActionMappings=(ActionName="LoadControlGroup0",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Zero) ++ActionMappings=(ActionName="LoadControlGroup1",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=One) ++ActionMappings=(ActionName="LoadControlGroup2",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Two) ++ActionMappings=(ActionName="LoadControlGroup3",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Three) ++ActionMappings=(ActionName="LoadControlGroup4",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Four) ++ActionMappings=(ActionName="LoadControlGroup5",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Five) ++ActionMappings=(ActionName="LoadControlGroup6",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Six) ++ActionMappings=(ActionName="LoadControlGroup7",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Seven) ++ActionMappings=(ActionName="LoadControlGroup8",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Eight) ++ActionMappings=(ActionName="LoadControlGroup9",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Nine) ++ActionMappings=(ActionName="ShowHealthBars",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftAlt) ++ActionMappings=(ActionName="ToggleSelection",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftControl) ++ActionMappings=(ActionName="AddSelection",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftShift) ++ActionMappings=(ActionName="ConfirmBuildingPlacement",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) ++ActionMappings=(ActionName="CancelBuildingPlacement",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) ++ActionMappings=(ActionName="CancelConstruction",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=C) ++ActionMappings=(ActionName="CancelProduction",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=C) ++AxisMappings=(AxisName="MoveCameraLeftRight",Scale=-1.000000,Key=Left) ++AxisMappings=(AxisName="MoveCameraLeftRight",Scale=1.000000,Key=Right) ++AxisMappings=(AxisName="MoveCameraUpDown",Scale=1.000000,Key=Up) ++AxisMappings=(AxisName="MoveCameraUpDown",Scale=-1.000000,Key=Down) ++AxisMappings=(AxisName="ZoomCamera",Scale=-1.000000,Key=MouseWheelAxis) +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +ConsoleKey=None +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde ++ConsoleKeys=Caret + + diff --git a/Source/RTSProject/Content/Buildings/Spacedock/BP_Spacedock.uasset b/Source/RTSProject/Content/Buildings/Spacedock/BP_Spacedock.uasset new file mode 100644 index 00000000..083dd7b6 Binary files /dev/null and b/Source/RTSProject/Content/Buildings/Spacedock/BP_Spacedock.uasset differ diff --git a/Source/RTSProject/Content/Buildings/Starbase/BP_Starbase.uasset b/Source/RTSProject/Content/Buildings/Starbase/BP_Starbase.uasset new file mode 100644 index 00000000..ac6b32e5 Binary files /dev/null and b/Source/RTSProject/Content/Buildings/Starbase/BP_Starbase.uasset differ diff --git a/Source/RTSProject/Content/Buildings/Starbase/M_Starbase.uasset b/Source/RTSProject/Content/Buildings/Starbase/M_Starbase.uasset new file mode 100644 index 00000000..efa5ff80 Binary files /dev/null and b/Source/RTSProject/Content/Buildings/Starbase/M_Starbase.uasset differ diff --git a/Source/RTSProject/Content/Buildings/Starbase/SM_Starbase.uasset b/Source/RTSProject/Content/Buildings/Starbase/SM_Starbase.uasset new file mode 100644 index 00000000..36aabcc7 Binary files /dev/null and b/Source/RTSProject/Content/Buildings/Starbase/SM_Starbase.uasset differ diff --git a/Source/RTSProject/Content/Buildings/Starbase/T_Starbase_Portrait.uasset b/Source/RTSProject/Content/Buildings/Starbase/T_Starbase_Portrait.uasset new file mode 100644 index 00000000..afa78b43 Binary files /dev/null and b/Source/RTSProject/Content/Buildings/Starbase/T_Starbase_Portrait.uasset differ diff --git a/Source/RTSProject/Content/Environment/M_Space.uasset b/Source/RTSProject/Content/Environment/M_Space.uasset new file mode 100644 index 00000000..1a23702e Binary files /dev/null and b/Source/RTSProject/Content/Environment/M_Space.uasset differ diff --git a/Source/RTSProject/Content/Environment/T_Space.uasset b/Source/RTSProject/Content/Environment/T_Space.uasset new file mode 100644 index 00000000..b1c41c03 Binary files /dev/null and b/Source/RTSProject/Content/Environment/T_Space.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSCheatManager.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSCheatManager.uasset new file mode 100644 index 00000000..ae36e0dc Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSCheatManager.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSGameMode.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSGameMode.uasset new file mode 100644 index 00000000..2601bc62 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSGameMode.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSGameState.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSGameState.uasset new file mode 100644 index 00000000..342b7a25 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSGameState.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSPawnAIController.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSPawnAIController.uasset new file mode 100644 index 00000000..455b1237 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSPawnAIController.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSPlayerAIController.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerAIController.uasset new file mode 100644 index 00000000..e521d67d Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerAIController.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSPlayerController.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerController.uasset new file mode 100644 index 00000000..a56a015e Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerController.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSPlayerPawn.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerPawn.uasset new file mode 100644 index 00000000..91cd7960 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerPawn.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSPlayerStart.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerStart.uasset new file mode 100644 index 00000000..11246d87 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerStart.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSPlayerState.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerState.uasset new file mode 100644 index 00000000..0f530b74 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSPlayerState.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSResourceType_Gas.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSResourceType_Gas.uasset new file mode 100644 index 00000000..384fc139 Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSResourceType_Gas.uasset differ diff --git a/Source/RTSProject/Content/Gameplay/BP_RTSResourceType_Ore.uasset b/Source/RTSProject/Content/Gameplay/BP_RTSResourceType_Ore.uasset new file mode 100644 index 00000000..cfb008fa Binary files /dev/null and b/Source/RTSProject/Content/Gameplay/BP_RTSResourceType_Ore.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/BP_TestGameMode.uasset b/Source/RTSProject/Content/Maps/AutomatedTests/BP_TestGameMode.uasset new file mode 100644 index 00000000..08a47c80 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/BP_TestGameMode.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestCombat/BP_TestCombat.uasset b/Source/RTSProject/Content/Maps/AutomatedTests/TestCombat/BP_TestCombat.uasset new file mode 100644 index 00000000..23f44a42 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestCombat/BP_TestCombat.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestCombat/TEST-Combat.umap b/Source/RTSProject/Content/Maps/AutomatedTests/TestCombat/TEST-Combat.umap new file mode 100644 index 00000000..14f0ab4a Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestCombat/TEST-Combat.umap differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestConstruction/BP_TestConstruction.uasset b/Source/RTSProject/Content/Maps/AutomatedTests/TestConstruction/BP_TestConstruction.uasset new file mode 100644 index 00000000..2e9faee3 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestConstruction/BP_TestConstruction.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestConstruction/TEST-Construction.umap b/Source/RTSProject/Content/Maps/AutomatedTests/TestConstruction/TEST-Construction.umap new file mode 100644 index 00000000..76cf3378 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestConstruction/TEST-Construction.umap differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestEconomy/BP_TestEconomy.uasset b/Source/RTSProject/Content/Maps/AutomatedTests/TestEconomy/BP_TestEconomy.uasset new file mode 100644 index 00000000..36eda4c9 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestEconomy/BP_TestEconomy.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestEconomy/TEST-Economy.umap b/Source/RTSProject/Content/Maps/AutomatedTests/TestEconomy/TEST-Economy.umap new file mode 100644 index 00000000..d0fcf6dc Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestEconomy/TEST-Economy.umap differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestMovement/BP_TestMovement.uasset b/Source/RTSProject/Content/Maps/AutomatedTests/TestMovement/BP_TestMovement.uasset new file mode 100644 index 00000000..cdacc883 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestMovement/BP_TestMovement.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestMovement/TEST-Movement.umap b/Source/RTSProject/Content/Maps/AutomatedTests/TestMovement/TEST-Movement.umap new file mode 100644 index 00000000..536e31f6 Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestMovement/TEST-Movement.umap differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestProduction/BP_TestProduction.uasset b/Source/RTSProject/Content/Maps/AutomatedTests/TestProduction/BP_TestProduction.uasset new file mode 100644 index 00000000..83b95b4d Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestProduction/BP_TestProduction.uasset differ diff --git a/Source/RTSProject/Content/Maps/AutomatedTests/TestProduction/TEST-Production.umap b/Source/RTSProject/Content/Maps/AutomatedTests/TestProduction/TEST-Production.umap new file mode 100644 index 00000000..3621eb6f Binary files /dev/null and b/Source/RTSProject/Content/Maps/AutomatedTests/TestProduction/TEST-Production.umap differ diff --git a/Source/RTSProject/Content/Maps/SKM-DarkSpace.umap b/Source/RTSProject/Content/Maps/SKM-DarkSpace.umap new file mode 100644 index 00000000..17eddd3c Binary files /dev/null and b/Source/RTSProject/Content/Maps/SKM-DarkSpace.umap differ diff --git a/Source/RTSProject/Content/Maps/SKM-DarkSpace_BuiltData.uasset b/Source/RTSProject/Content/Maps/SKM-DarkSpace_BuiltData.uasset new file mode 100644 index 00000000..4079239c Binary files /dev/null and b/Source/RTSProject/Content/Maps/SKM-DarkSpace_BuiltData.uasset differ diff --git a/Source/RTSProject/Content/Materials/M_RTSWhiteFogOfWar.uasset b/Source/RTSProject/Content/Materials/M_RTSWhiteFogOfWar.uasset new file mode 100644 index 00000000..ab73cd3a Binary files /dev/null and b/Source/RTSProject/Content/Materials/M_RTSWhiteFogOfWar.uasset differ diff --git a/Source/RTSProject/Content/Materials/M_RTSWhiteFogOfWarMinimap.uasset b/Source/RTSProject/Content/Materials/M_RTSWhiteFogOfWarMinimap.uasset new file mode 100644 index 00000000..2f96ea5e Binary files /dev/null and b/Source/RTSProject/Content/Materials/M_RTSWhiteFogOfWarMinimap.uasset differ diff --git a/Source/RTSProject/Content/Resources/BP_OreAsteroid.uasset b/Source/RTSProject/Content/Resources/BP_OreAsteroid.uasset new file mode 100644 index 00000000..fa50de32 Binary files /dev/null and b/Source/RTSProject/Content/Resources/BP_OreAsteroid.uasset differ diff --git a/Source/RTSProject/Content/Resources/M_OreAsteroid.uasset b/Source/RTSProject/Content/Resources/M_OreAsteroid.uasset new file mode 100644 index 00000000..08f4de54 Binary files /dev/null and b/Source/RTSProject/Content/Resources/M_OreAsteroid.uasset differ diff --git a/Source/RTSProject/Content/Resources/SM_OreAsteroid.uasset b/Source/RTSProject/Content/Resources/SM_OreAsteroid.uasset new file mode 100644 index 00000000..d43ed5ca Binary files /dev/null and b/Source/RTSProject/Content/Resources/SM_OreAsteroid.uasset differ diff --git a/Source/RTSProject/Content/Resources/T_OreAsteroid_Portrait.uasset b/Source/RTSProject/Content/Resources/T_OreAsteroid_Portrait.uasset new file mode 100644 index 00000000..4a984b9b Binary files /dev/null and b/Source/RTSProject/Content/Resources/T_OreAsteroid_Portrait.uasset differ diff --git a/Source/RTSProject/Content/UI/BP_RTSHUD.uasset b/Source/RTSProject/Content/UI/BP_RTSHUD.uasset new file mode 100644 index 00000000..65925431 Binary files /dev/null and b/Source/RTSProject/Content/UI/BP_RTSHUD.uasset differ diff --git a/Source/RTSProject/Content/UI/Fonts/FF_NotoSansDisplay-Bold.uasset b/Source/RTSProject/Content/UI/Fonts/FF_NotoSansDisplay-Bold.uasset new file mode 100644 index 00000000..674d57d4 Binary files /dev/null and b/Source/RTSProject/Content/UI/Fonts/FF_NotoSansDisplay-Bold.uasset differ diff --git a/Source/RTSProject/Content/UI/Fonts/FF_NotoSansDisplay-Regular.uasset b/Source/RTSProject/Content/UI/Fonts/FF_NotoSansDisplay-Regular.uasset new file mode 100644 index 00000000..687d4126 Binary files /dev/null and b/Source/RTSProject/Content/UI/Fonts/FF_NotoSansDisplay-Regular.uasset differ diff --git a/Source/RTSProject/Content/UI/Fonts/F_NotoSansDisplay.uasset b/Source/RTSProject/Content/UI/Fonts/F_NotoSansDisplay.uasset new file mode 100644 index 00000000..90f495a8 Binary files /dev/null and b/Source/RTSProject/Content/UI/Fonts/F_NotoSansDisplay.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/BP_RTSConstructionProgressBarWidgetComponent.uasset b/Source/RTSProject/Content/UI/Widgets/BP_RTSConstructionProgressBarWidgetComponent.uasset new file mode 100644 index 00000000..49db5822 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/BP_RTSConstructionProgressBarWidgetComponent.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/BP_RTSHealthBarWidgetComponent.uasset b/Source/RTSProject/Content/UI/Widgets/BP_RTSHealthBarWidgetComponent.uasset new file mode 100644 index 00000000..dbab6339 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/BP_RTSHealthBarWidgetComponent.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/BP_RTSHoveredActorWidgetComponent.uasset b/Source/RTSProject/Content/UI/Widgets/BP_RTSHoveredActorWidgetComponent.uasset new file mode 100644 index 00000000..05e2cabb Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/BP_RTSHoveredActorWidgetComponent.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/BP_RTSProductionProgressBarWidgetComponent.uasset b/Source/RTSProject/Content/UI/Widgets/BP_RTSProductionProgressBarWidgetComponent.uasset new file mode 100644 index 00000000..1d302945 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/BP_RTSProductionProgressBarWidgetComponent.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/BP_RTSResourcesReturnedFloatingCombatTextComponent.uasset b/Source/RTSProject/Content/UI/Widgets/BP_RTSResourcesReturnedFloatingCombatTextComponent.uasset new file mode 100644 index 00000000..cff8c1e4 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/BP_RTSResourcesReturnedFloatingCombatTextComponent.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/DT_TooltipRichTextStyle.uasset b/Source/RTSProject/Content/UI/Widgets/DT_TooltipRichTextStyle.uasset new file mode 100644 index 00000000..452aa5fe Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/DT_TooltipRichTextStyle.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_CommandGrid.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_CommandGrid.uasset new file mode 100644 index 00000000..ff405f24 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_CommandGrid.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_CommandGridButton.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_CommandGridButton.uasset new file mode 100644 index 00000000..d39e49df Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_CommandGridButton.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_ConstructionProgressBar.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_ConstructionProgressBar.uasset new file mode 100644 index 00000000..ae5280a6 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_ConstructionProgressBar.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_HealthBar.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_HealthBar.uasset new file mode 100644 index 00000000..0a37eea6 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_HealthBar.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_HoveredActorLabel.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_HoveredActorLabel.uasset new file mode 100644 index 00000000..342046f3 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_HoveredActorLabel.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_IngameUI.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_IngameUI.uasset new file mode 100644 index 00000000..debdd868 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_IngameUI.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_MinimapWidget.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_MinimapWidget.uasset new file mode 100644 index 00000000..550c1591 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_MinimapWidget.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_ProductionProgressBar.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_ProductionProgressBar.uasset new file mode 100644 index 00000000..82fa68d1 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_ProductionProgressBar.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_ResourcePanel.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_ResourcePanel.uasset new file mode 100644 index 00000000..439be29b Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_ResourcePanel.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_SelectionFrame.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_SelectionFrame.uasset new file mode 100644 index 00000000..9969534a Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_SelectionFrame.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_StatusWindow.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_StatusWindow.uasset new file mode 100644 index 00000000..45c67a99 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_StatusWindow.uasset differ diff --git a/Source/RTSProject/Content/UI/Widgets/WBP_Tooltip.uasset b/Source/RTSProject/Content/UI/Widgets/WBP_Tooltip.uasset new file mode 100644 index 00000000..f29288f6 Binary files /dev/null and b/Source/RTSProject/Content/UI/Widgets/WBP_Tooltip.uasset differ diff --git a/Source/RTSProject/Content/Units/CMV/BP_CMV.uasset b/Source/RTSProject/Content/Units/CMV/BP_CMV.uasset new file mode 100644 index 00000000..d2e5a4b2 Binary files /dev/null and b/Source/RTSProject/Content/Units/CMV/BP_CMV.uasset differ diff --git a/Source/RTSProject/Content/Units/CMV/BP_CMV_Projectile.uasset b/Source/RTSProject/Content/Units/CMV/BP_CMV_Projectile.uasset new file mode 100644 index 00000000..7923e61b Binary files /dev/null and b/Source/RTSProject/Content/Units/CMV/BP_CMV_Projectile.uasset differ diff --git a/Source/RTSProject/Content/Units/CMV/M_CMV.uasset b/Source/RTSProject/Content/Units/CMV/M_CMV.uasset new file mode 100644 index 00000000..eecc2db5 Binary files /dev/null and b/Source/RTSProject/Content/Units/CMV/M_CMV.uasset differ diff --git a/Source/RTSProject/Content/Units/CMV/SM_CMV.uasset b/Source/RTSProject/Content/Units/CMV/SM_CMV.uasset new file mode 100644 index 00000000..553d0391 Binary files /dev/null and b/Source/RTSProject/Content/Units/CMV/SM_CMV.uasset differ diff --git a/Source/RTSProject/Content/Units/CMV/T_CMV_Portrait.uasset b/Source/RTSProject/Content/Units/CMV/T_CMV_Portrait.uasset new file mode 100644 index 00000000..55a5f2b0 Binary files /dev/null and b/Source/RTSProject/Content/Units/CMV/T_CMV_Portrait.uasset differ diff --git a/Source/RTSProject/Content/Units/Frigate/BP_Frigate.uasset b/Source/RTSProject/Content/Units/Frigate/BP_Frigate.uasset new file mode 100644 index 00000000..a68266e9 Binary files /dev/null and b/Source/RTSProject/Content/Units/Frigate/BP_Frigate.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/BP_DaeTestBlueprintMacroLibrary.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/BP_DaeTestBlueprintMacroLibrary.uasset new file mode 100644 index 00000000..32294698 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/BP_DaeTestBlueprintMacroLibrary.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysFail/BP_TestAlwaysFail.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysFail/BP_TestAlwaysFail.uasset new file mode 100644 index 00000000..9bea002c Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysFail/BP_TestAlwaysFail.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysFail/TestAlwaysFail.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysFail/TestAlwaysFail.umap new file mode 100644 index 00000000..2beaf7c7 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysFail/TestAlwaysFail.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSkip/BP_TestAlwaysSkip.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSkip/BP_TestAlwaysSkip.uasset new file mode 100644 index 00000000..f3668d76 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSkip/BP_TestAlwaysSkip.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSkip/TestAlwaysSkip.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSkip/TestAlwaysSkip.umap new file mode 100644 index 00000000..b87f74f0 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSkip/TestAlwaysSkip.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceed/BP_TestAlwaysSucceed.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceed/BP_TestAlwaysSucceed.uasset new file mode 100644 index 00000000..650d779f Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceed/BP_TestAlwaysSucceed.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceed/TestAlwaysSucceed.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceed/TestAlwaysSucceed.umap new file mode 100644 index 00000000..ba948a50 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceed/TestAlwaysSucceed.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceedWithDelay/BP_TestAlwaysSucceedWithDelay.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceedWithDelay/BP_TestAlwaysSucceedWithDelay.uasset new file mode 100644 index 00000000..e8028921 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceedWithDelay/BP_TestAlwaysSucceedWithDelay.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceedWithDelay/TestAlwaysSucceedWithDelay.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceedWithDelay/TestAlwaysSucceedWithDelay.umap new file mode 100644 index 00000000..5dfd359e Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysSucceedWithDelay/TestAlwaysSucceedWithDelay.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysTimeout/BP_TestAlwaysTimeout.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysTimeout/BP_TestAlwaysTimeout.uasset new file mode 100644 index 00000000..bc70630b Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysTimeout/BP_TestAlwaysTimeout.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysTimeout/TestAlwaysTimeout.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysTimeout/TestAlwaysTimeout.umap new file mode 100644 index 00000000..12f89327 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAlwaysTimeout/TestAlwaysTimeout.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAssume/BP_TestAssume.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAssume/BP_TestAssume.uasset new file mode 100644 index 00000000..7d871c78 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAssume/BP_TestAssume.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAssume/TestAssume.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAssume/TestAssume.umap new file mode 100644 index 00000000..21a42ca2 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestAssume/TestAssume.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/BP_TestBeforeAfter.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/BP_TestBeforeAfter.uasset new file mode 100644 index 00000000..6ce279e0 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/BP_TestBeforeAfter.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/BP_TestSuiteBeforeAfter.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/BP_TestSuiteBeforeAfter.uasset new file mode 100644 index 00000000..a339fc6d Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/BP_TestSuiteBeforeAfter.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/TestBeforeAfter.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/TestBeforeAfter.umap new file mode 100644 index 00000000..a769530a Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestBeforeAfter/TestBeforeAfter.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/BP_TestCalculator.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/BP_TestCalculator.uasset new file mode 100644 index 00000000..7d23411e Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/BP_TestCalculator.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/BP_TestCalculatorAddsNumbers.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/BP_TestCalculatorAddsNumbers.uasset new file mode 100644 index 00000000..f2fb397b Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/BP_TestCalculatorAddsNumbers.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/TestCalculator.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/TestCalculator.umap new file mode 100644 index 00000000..8c8760fe Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCalculator/TestCalculator.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCompare/BP_TestCompare.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCompare/BP_TestCompare.uasset new file mode 100644 index 00000000..c1b76326 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCompare/BP_TestCompare.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCompare/TestCompare.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCompare/TestCompare.umap new file mode 100644 index 00000000..be88e595 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestCompare/TestCompare.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestEquals/BP_TestEquals.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestEquals/BP_TestEquals.uasset new file mode 100644 index 00000000..2480fbd5 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestEquals/BP_TestEquals.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestEquals/TestEquals.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestEquals/TestEquals.umap new file mode 100644 index 00000000..b010136a Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestEquals/TestEquals.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameter1.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameter1.uasset new file mode 100644 index 00000000..2025c30f Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameter1.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameter2.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameter2.uasset new file mode 100644 index 00000000..4c00114c Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameter2.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameterProvider.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameterProvider.uasset new file mode 100644 index 00000000..10805bb1 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameterProvider.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameterized.uasset b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameterized.uasset new file mode 100644 index 00000000..e52f82e8 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/BP_TestParameterized.uasset differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/TestParameterized.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/TestParameterized.umap new file mode 100644 index 00000000..12765880 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/TestParameterized.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/TestParameterized_Streaming.umap b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/TestParameterized_Streaming.umap new file mode 100644 index 00000000..311a4a85 Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Content/TestParameterized/TestParameterized_Streaming.umap differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/DaedalicTestAutomationPlugin.uplugin b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/DaedalicTestAutomationPlugin.uplugin new file mode 100644 index 00000000..85389ee0 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/DaedalicTestAutomationPlugin.uplugin @@ -0,0 +1,36 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0.0", + "FriendlyName": "Daedalic Test Automation Plugin", + "Description": "Facilitates setting up integration test suits with Gauntlet.", + "Category": "Daedalic Entertainment", + "CreatedBy": "Daedalic Entertainment GmbH", + "CreatedByURL": "https://www.daedalic.com/", + "DocsURL": "https://github.com/DaedalicEntertainment/ue4-test-automation", + "EngineVersion": "4.25.0", + "MarketplaceURL": "", + "SupportURL": "https://github.com/DaedalicEntertainment/ue4-test-automation/issues", + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "DaedalicTestAutomationPlugin", + "Type": "DeveloperTool", + "LoadingPhase": "Default" + }, + { + "Name": "DaedalicTestAutomationPluginEditor", + "Type": "Editor", + "LoadingPhase": "PreDefault" + } + ], + "Plugins": [ + { + "Name": "Gauntlet", + "Enabled": true + } + ] +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Resources/Icon128.png b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Resources/Icon128.png new file mode 100644 index 00000000..3033ae0e Binary files /dev/null and b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Resources/Icon128.png differ diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/DaedalicTestAutomationPlugin.Build.cs b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/DaedalicTestAutomationPlugin.Build.cs new file mode 100644 index 00000000..64ddcb13 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/DaedalicTestAutomationPlugin.Build.cs @@ -0,0 +1,51 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class DaedalicTestAutomationPlugin : ModuleRules + { + public DaedalicTestAutomationPlugin(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "Gauntlet", + "UMG", + "SlateCore" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + // ... add private dependencies that you statically link with here ... + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeDelayFramesAction.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeDelayFramesAction.cpp new file mode 100644 index 00000000..b568b3f3 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeDelayFramesAction.cpp @@ -0,0 +1,24 @@ +#include "DaeDelayFramesAction.h" + +FDaeDelayFramesAction::FDaeDelayFramesAction(const FLatentActionInfo& LatentInfo, int32 NumFrames) + : FramesRemaining(NumFrames) + , ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) +{ +} + +void FDaeDelayFramesAction::UpdateOperation(FLatentResponse& Response) + +{ + --FramesRemaining; + Response.FinishAndTriggerIf(FramesRemaining <= 0, ExecutionFunction, OutputLink, + CallbackTarget); +} + +#if WITH_EDITOR +FString FDaeDelayFramesAction::GetDescription() const +{ + return FString::Printf(TEXT("Delay ({0} frames left)"), FramesRemaining); +} +#endif diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeDelayUntilTriggeredAction.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeDelayUntilTriggeredAction.cpp new file mode 100644 index 00000000..a76d3996 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeDelayUntilTriggeredAction.cpp @@ -0,0 +1,26 @@ +#include "DaeDelayUntilTriggeredAction.h" +#include "DaeTestTriggerBox.h" + +FDaeDelayUntilTriggeredAction::FDaeDelayUntilTriggeredAction(const FLatentActionInfo& LatentInfo, + ADaeTestTriggerBox* InTestTriggerBox) + : TestTriggerBox(InTestTriggerBox) + , ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) +{ +} + +void FDaeDelayUntilTriggeredAction::UpdateOperation(FLatentResponse& Response) + +{ + bool bWasTriggered = !IsValid(TestTriggerBox) || TestTriggerBox->WasTriggered(); + Response.FinishAndTriggerIf(bWasTriggered, ExecutionFunction, OutputLink, CallbackTarget); +} + +#if WITH_EDITOR +FString FDaeDelayUntilTriggeredAction::GetDescription() const +{ + FString TriggerBoxName = IsValid(TestTriggerBox) ? TestTriggerBox->GetName() : TEXT("nullptr"); + return FString::Printf(TEXT("Delay (until {0} was triggered)"), *TriggerBoxName); +} +#endif diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeGauntletStates.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeGauntletStates.cpp new file mode 100644 index 00000000..dcd51bc4 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeGauntletStates.cpp @@ -0,0 +1,6 @@ +#include "DaeGauntletStates.h" + +FName FDaeGauntletStates::LoadingNextMap = TEXT("Gauntlet_LoadingNextMap"); +FName FDaeGauntletStates::DiscoveringTests = TEXT("Gauntlet_DiscoveringTests"); +FName FDaeGauntletStates::Running = TEXT("Gauntlet_Running"); +FName FDaeGauntletStates::Finished = TEXT("Gauntlet_Finished"); diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeGauntletTestController.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeGauntletTestController.cpp new file mode 100644 index 00000000..3bf5ff39 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeGauntletTestController.cpp @@ -0,0 +1,194 @@ +#include "DaeGauntletTestController.h" +#include "Kismet/GameplayStatics.h" +#include "DaeGauntletStates.h" +#include "DaeJUnitReportWriter.h" +#include "DaeTestAutomationPluginSettings.h" +#include "DaeTestLogCategory.h" +#include "DaeTestSuiteActor.h" +#include +#include +#include +#include + +void UDaeGauntletTestController::OnInit() +{ + Super::OnInit(); + + // Get tests path. + const UDaeTestAutomationPluginSettings* TestAutomationPluginSettings = + GetDefault(); + + UE_LOG(LogDaeTest, Display, TEXT("Discovering tests from: %s"), + *TestAutomationPluginSettings->TestMapPath); + + // Build list of tests (based on FAutomationEditorCommonUtils::CollectTestsByClass). + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + TArray AssetDataArray; + + AssetRegistryModule.Get().SearchAllAssets(true); + AssetRegistryModule.Get().GetAssetsByClass(UWorld::StaticClass()->GetFName(), AssetDataArray); + + const FString PatternToCheck = + FString::Printf(TEXT("/%s/"), *TestAutomationPluginSettings->TestMapPath); + + for (auto ObjIter = AssetDataArray.CreateConstIterator(); ObjIter; ++ObjIter) + { + const FAssetData& AssetData = *ObjIter; + + FString Filename = FPackageName::LongPackageNameToFilename(AssetData.ObjectPath.ToString()); + + if (Filename.Contains(*PatternToCheck)) + { + FName MapName = AssetData.AssetName; + MapNames.Add(MapName); + + UE_LOG(LogDaeTest, Display, TEXT("Discovered test: %s"), *MapName.ToString()); + } + } + + GetGauntlet()->BroadcastStateChange(FDaeGauntletStates::Initialized); +} + +void UDaeGauntletTestController::OnPostMapChange(UWorld* World) +{ + Super::OnPostMapChange(World); + + UE_LOG(LogDaeTest, Log, TEXT("UDaeGauntletTestController::OnPostMapChange - World: %s"), + *World->GetName()); + + if (GetCurrentState() != FDaeGauntletStates::LoadingNextMap) + { + return; + } + + GetGauntlet()->BroadcastStateChange(FDaeGauntletStates::DiscoveringTests); +} + +void UDaeGauntletTestController::OnTick(float TimeDelta) +{ + if (GetCurrentState() == FDaeGauntletStates::Initialized) + { + // If this isn't a test map (e.g. immediately after startup), load first test map now. + if (!MapNames.Contains(FName(*GetCurrentMap()))) + { + UE_LOG(LogDaeTest, Log, + TEXT("FDaeGauntletStates::Initialized - World is not a test world, " + "loading first test world.")); + + MapIndex = -1; + LoadNextTestMap(); + return; + } + else + { + GetGauntlet()->BroadcastStateChange(FDaeGauntletStates::DiscoveringTests); + } + } + else if (GetCurrentState() == FDaeGauntletStates::LoadingNextMap) + { + UE_LOG(LogDaeTest, Display, TEXT("FDaeGauntletStates::LoadingNextMap - Loading map: %s"), + *MapNames[MapIndex].ToString()); + + UGameplayStatics::OpenLevel(this, MapNames[MapIndex]); + } + else if (GetCurrentState() == FDaeGauntletStates::DiscoveringTests) + { + // Find test suite. + ADaeTestSuiteActor* TestSuite = nullptr; + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + TestSuite = *ActorIt; + } + + if (!IsValid(TestSuite)) + { + UE_LOG(LogDaeTest, Error, + TEXT("FDaeGauntletStates::DiscoveringTests - No " + "DaeGauntletTestSuiteActor " + "found.")); + LoadNextTestMap(); + return; + } + + // Start first test. + GetGauntlet()->BroadcastStateChange(FDaeGauntletStates::Running); + + TestSuite->OnTestSuiteSuccessful.AddDynamic( + this, &UDaeGauntletTestController::OnTestSuiteFinished); + TestSuite->OnTestSuiteFailed.AddDynamic(this, + &UDaeGauntletTestController::OnTestSuiteFinished); + + TestSuite->RunAllTests(); + } +} + +void UDaeGauntletTestController::LoadNextTestMap() +{ + ++MapIndex; + + // Check if we just want to run a single test. + FString SingleTestName = ParseCommandLineOption(TEXT("TestName")); + + if (!SingleTestName.IsEmpty()) + { + while (MapNames.IsValidIndex(MapIndex) && MapNames[MapIndex].ToString() != SingleTestName) + { + ++MapIndex; + } + } + + if (MapNames.IsValidIndex(MapIndex)) + { + // Load next test map in next tick. This is to avoid invocation list changes during OnPostMapChange. + GetGauntlet()->BroadcastStateChange(FDaeGauntletStates::LoadingNextMap); + } + else + { + // All tests finished. + UE_LOG(LogDaeTest, Display, + TEXT("UDaeGauntletTestController::LoadNextTestMap - All tests finished.")); + + // Write final test report. + FDaeJUnitReportWriter JUnitReportWriter; + FString TestReport = + JUnitReportWriter.CreateReport(TEXT("DaeGauntletTestController"), Results); + UE_LOG(LogDaeTest, Log, TEXT("Test report:\r\n%s"), *TestReport); + + FString JUnitReportPath = ParseCommandLineOption(TEXT("JUnitReportPath")); + + if (!JUnitReportPath.IsEmpty()) + { + UE_LOG(LogDaeTest, Display, TEXT("Writing test report to: %s"), *JUnitReportPath); + FFileHelper::SaveStringToFile(TestReport, *JUnitReportPath); + } + + // Finish Gauntlet. + GetGauntlet()->BroadcastStateChange(FDaeGauntletStates::Finished); + + for (const FDaeTestSuiteResult& Result : Results) + { + if (Result.NumFailedTests() > 0) + { + EndTest(1); + return; + } + } + + EndTest(0); + } +} + +void UDaeGauntletTestController::OnTestSuiteFinished(ADaeTestSuiteActor* TestSuite) +{ + Results.Add(TestSuite->GetResult()); + LoadNextTestMap(); +} + +FString UDaeGauntletTestController::ParseCommandLineOption(const FString& Key) +{ + FString Value; + FParse::Value(FCommandLine::Get(), *Key, Value); + return Value.Mid(1); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeJUnitReportWriter.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeJUnitReportWriter.cpp new file mode 100644 index 00000000..a5e7f0a9 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeJUnitReportWriter.cpp @@ -0,0 +1,109 @@ +#include "DaeJUnitReportWriter.h" + +FString FDaeJUnitReportWriter::CreateReport(const FString& Name, + const TArray& TestSuites) const +{ + // Write a JUnit XML report based on FXmlFile::WriteNodeHierarchy. + // Unfortunately, FXmlNode::Tag is private, so we have to do the hard work here ourselves... + FString XmlString; + + XmlString += TEXT("") LINE_TERMINATOR; + XmlString += TEXT("") LINE_TERMINATOR; + + for (const FDaeTestSuiteResult& TestSuiteResult : TestSuites) + { + const FString& TestClassName = FString::Printf(TEXT("%s.%s"), *TestSuiteResult.MapName, + *TestSuiteResult.TestSuiteName); + + for (const FDaeTestResult& TestResult : TestSuiteResult.TestResults) + { + XmlString += TEXT(" ") LINE_TERMINATOR; + + if (TestResult.HasFailed()) + { + XmlString += + FString::Printf(TEXT(" %s"), + *TestResult.FailureMessage) + + LINE_TERMINATOR; + } + else if (TestResult.WasSkipped()) + { + XmlString += + FString::Printf(TEXT(" %s"), *TestResult.SkipReason) + + LINE_TERMINATOR; + } + + XmlString += TEXT(" ") LINE_TERMINATOR; + } + } + + XmlString += TEXT("") LINE_TERMINATOR; + + return XmlString; +} + +int32 FDaeJUnitReportWriter::NumTotalTests(const TArray& TestSuites) const +{ + int32 TotalTests = 0; + + for (const FDaeTestSuiteResult& TestSuite : TestSuites) + { + TotalTests += TestSuite.NumTotalTests(); + } + + return TotalTests; +} + +int32 FDaeJUnitReportWriter::NumFailedTests(const TArray& TestSuites) const +{ + int32 FailedTests = 0; + + for (const FDaeTestSuiteResult& TestSuite : TestSuites) + { + FailedTests += TestSuite.NumFailedTests(); + } + + return FailedTests; +} + +int32 FDaeJUnitReportWriter::NumSkippedTests(const TArray& TestSuites) const +{ + int32 SkippedTests = 0; + + for (const FDaeTestSuiteResult& TestSuite : TestSuites) + { + SkippedTests += TestSuite.NumSkippedTests(); + } + + return SkippedTests; +} + +float FDaeJUnitReportWriter::GetTotalTimeSeconds(const TArray& TestSuites) const +{ + float TimeSeconds = 0; + + for (const FDaeTestSuiteResult& TestSuite : TestSuites) + { + TimeSeconds += TestSuite.GetTotalTimeSeconds(); + } + + return TimeSeconds; +} + +FString FDaeJUnitReportWriter::GetTimestamp(const TArray& TestSuites) const +{ + FDateTime Timestamp = TestSuites.Num() > 0 ? TestSuites[0].Timestamp : FDateTime::UtcNow(); + return Timestamp.ToIso8601(); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestActor.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestActor.cpp new file mode 100644 index 00000000..ea2c7dcf --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestActor.cpp @@ -0,0 +1,156 @@ +#include "DaeTestActor.h" +#include "DaeTestLogCategory.h" +#include "DaeTestParameterProviderActor.h" + +ADaeTestActor::ADaeTestActor( + const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + TimeoutInSeconds = 30.0f; +} + +void ADaeTestActor::ApplyParameterProviders() +{ + for (int32 Index = 0; Index < ParameterProviders.Num(); ++Index) + { + ADaeTestParameterProviderActor* Provider = ParameterProviders[Index]; + + if (!IsValid(Provider)) + { + UE_LOG(LogDaeTest, Error, + TEXT("ADaeTestActor::ApplyParameterProviders - %s has invalid parameter " + "provider at index %i, skipping."), + *GetName(), Index); + + continue; + } + + TArray AdditionalParameters = Provider->GetParameters(); + Parameters.Append(AdditionalParameters); + + UE_LOG(LogDaeTest, Log, + TEXT("ADaeTestActor::ApplyParameterProviders - %s appended %i additional parameters " + "provided by %s."), + *GetName(), AdditionalParameters.Num(), *Provider->GetName()); + } +} + +void ADaeTestActor::RunTest(UObject* TestParameter) +{ + CurrentParameter = TestParameter; + bHasResult = false; + + if (!SkipReason.IsEmpty()) + { + NotifyOnTestSkipped(SkipReason); + return; + } + + NotifyOnAssume(CurrentParameter); + + if (bHasResult) + { + // This can happen with failed assumptions, for instance. + return; + } + + NotifyOnArrange(CurrentParameter); + NotifyOnAct(CurrentParameter); +} + +void ADaeTestActor::FinishAct() +{ + if (bHasResult) + { + UE_LOG(LogDaeTest, Warning, + TEXT("Test %s already has a result. This can happen after a timeout due to delays, " + "but if not, make sure not to call FinishAct more than once."), + *GetName()); + return; + } + + NotifyOnAssert(CurrentParameter); + + if (!bHasResult) + { + NotifyOnTestSuccessful(); + } +} + +float ADaeTestActor::GetTimeoutInSeconds() const +{ + return TimeoutInSeconds; +} + +TArray> ADaeTestActor::GetParameters() const +{ + return Parameters; +} + +UObject* ADaeTestActor::GetCurrentParameter() const +{ + return CurrentParameter; +} + +void ADaeTestActor::NotifyOnTestSuccessful() +{ + if (bHasResult) + { + return; + } + + bHasResult = true; + + OnTestSuccessful.Broadcast(this, CurrentParameter); +} + +void ADaeTestActor::NotifyOnTestFailed(const FString& Message) +{ + if (bHasResult) + { + return; + } + + bHasResult = true; + + UE_LOG(LogDaeTest, Error, TEXT("%s"), *Message); + + OnTestFailed.Broadcast(this, CurrentParameter, Message); +} + +void ADaeTestActor::NotifyOnTestSkipped(const FString& InSkipReason) +{ + if (bHasResult) + { + return; + } + + bHasResult = true; + + OnTestSkipped.Broadcast(this, CurrentParameter, InSkipReason); +} + +void ADaeTestActor::NotifyOnAssume(UObject* Parameter) +{ + ReceiveOnAssume(Parameter); +} + +void ADaeTestActor::NotifyOnArrange(UObject* Parameter) +{ + ReceiveOnArrange(Parameter); +} + +void ADaeTestActor::NotifyOnAct(UObject* Parameter) +{ + ReceiveOnAct(Parameter); +} + +void ADaeTestActor::NotifyOnAssert(UObject* Parameter) +{ + ReceiveOnAssert(Parameter); +} + +void ADaeTestActor::ReceiveOnAct_Implementation(UObject* Parameter) +{ + FinishAct(); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestActorBlueprint.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestActorBlueprint.cpp new file mode 100644 index 00000000..da81d7e0 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestActorBlueprint.cpp @@ -0,0 +1 @@ +#include "DaeTestActorBlueprint.h" diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAssertBlueprintFunctionLibrary.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAssertBlueprintFunctionLibrary.cpp new file mode 100644 index 00000000..d36d5161 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAssertBlueprintFunctionLibrary.cpp @@ -0,0 +1,595 @@ +#include "DaeTestAssertBlueprintFunctionLibrary.h" +#include "DaeTestActor.h" +#include "DaeTestLogCategory.h" +#include "DaeTestTriggerBox.h" +#include +#include +#include +#include +#include +#include +#include + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatEqual = + TEXT("Assertion failed - {0} - Expected: {1}, but was: {2}"); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatNotEqual = + TEXT("Assertion failed - {0} - Was {1}, but should not be."); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatLessThan = + TEXT("Assertion failed - {0} - Expected: {1} < {2}, but was not."); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatLessThanOrEqualTo = + TEXT("Assertion failed - {0} - Expected: {1} <= {2}, but was not."); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatGreaterThan = + TEXT("Assertion failed - {0} - Expected: {1} > {2}, but was not."); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatGreaterThanOrEqualTo = + TEXT("Assertion failed - {0} - Expected: {1} >= {2}, but was not."); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatInRange = + TEXT("Assertion failed - {0} - Expected: between {1} and {2}, but was: {3}"); + +const FString UDaeTestAssertBlueprintFunctionLibrary::ErrorMessageFormatNotInRange = + TEXT("Assertion failed - {0} - Expected: not between {1} and {2}, but was: {3}"); + +void UDaeTestAssertBlueprintFunctionLibrary::AssertFail(const FString& What, + UObject* Context /*= nullptr*/) +{ + OnTestFailed(Context, What); +} +void UDaeTestAssertBlueprintFunctionLibrary::AssertTrue(bool bValue, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!bValue) + { + FString Message = + FString::Format(*ErrorMessageFormatEqual, {What, TEXT("True"), TEXT("False")}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertFalse(bool bValue, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (bValue) + { + FString Message = + FString::Format(*ErrorMessageFormatEqual, {What, TEXT("False"), TEXT("True")}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertValid(UObject* Object, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!IsValid(Object)) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Expected: valid, but was: invalid"), + {What}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertInvalid(UObject* Object, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (IsValid(Object)) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Expected: invalid, but was: valid"), + {What}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertWasTriggered(ADaeTestTriggerBox* TestTriggerBox, + UObject* Context /*= nullptr*/) +{ + if (!IsValid(TestTriggerBox)) + { + OnTestFailed(Context, TEXT("Invalid test trigger box in assertion")); + return; + } + + if (!TestTriggerBox->WasTriggered()) + { + FString Message = + FString::Format(TEXT("Assertion failed - Trigger box {0} wasn't triggered"), + {TestTriggerBox->GetName()}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertWasNotTriggered( + ADaeTestTriggerBox* TestTriggerBox, UObject* Context /*= nullptr*/) +{ + if (!IsValid(TestTriggerBox)) + { + OnTestFailed(Context, TEXT("Invalid test trigger box in assertion")); + return; + } + + if (TestTriggerBox->WasTriggered()) + { + FString Message = FString::Format(TEXT("Assertion failed - Trigger box {0} was triggered"), + {TestTriggerBox->GetName()}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualByte(uint8 Actual, uint8 Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertEqual(Context, What, Actual, Expected); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualByte(uint8 Actual, uint8 Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertNotEqual(Context, What, Actual, Unexpected); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertCompareByte(uint8 First, + EDaeTestComparisonMethod ShouldBe, + uint8 Second, const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertCompare(Context, What, First, ShouldBe, Second); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualInt32(int32 Actual, int32 Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertEqual(Context, What, Actual, Expected); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualInt32(int32 Actual, int32 Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertNotEqual(Context, What, Actual, Unexpected); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertCompareInt32(int32 First, + EDaeTestComparisonMethod ShouldBe, + int32 Second, const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertCompare(Context, What, First, ShouldBe, Second); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualInt64(int64 Actual, int64 Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertEqual(Context, What, Actual, Expected); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualInt64(int64 Actual, int64 Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertNotEqual(Context, What, Actual, Unexpected); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertCompareInt64(int64 First, + EDaeTestComparisonMethod ShouldBe, + int64 Second, const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertCompare(Context, What, First, ShouldBe, Second); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualFloat(float Actual, float Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!FMath::IsNearlyEqual(Actual, Expected)) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, {What, Expected, Actual}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualFloat(float Actual, float Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (FMath::IsNearlyEqual(Actual, Unexpected)) + { + FString Message = FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertCompareFloat(float First, + EDaeTestComparisonMethod ShouldBe, + float Second, const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertCompare(Context, What, First, ShouldBe, Second); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualName(const FName& Actual, + const FName& Expected, + bool bIgnoreCase, const FString& What, + UObject* Context /*= nullptr*/) +{ + bool bEquals = bIgnoreCase ? Actual.IsEqual(Expected, ENameCase::IgnoreCase) + : Actual.IsEqual(Expected, ENameCase::CaseSensitive); + + if (!bEquals) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, + {What, Expected.ToString(), Actual.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualName(const FName& Actual, + const FName& Unexpected, + bool bIgnoreCase, + const FString& What, + UObject* Context /*= nullptr*/) +{ + bool bEquals = bIgnoreCase ? Actual.IsEqual(Unexpected, ENameCase::IgnoreCase) + : Actual.IsEqual(Unexpected, ENameCase::CaseSensitive); + + if (bEquals) + { + FString Message = + FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualString(const FString& Actual, + const FString& Expected, + bool bIgnoreCase, + const FString& What, + UObject* Context /*= nullptr*/) +{ + ESearchCase::Type SearchCase = bIgnoreCase ? ESearchCase::IgnoreCase + : ESearchCase::CaseSensitive; + + if (!Actual.Equals(Expected, SearchCase)) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, {What, Expected, Actual}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualString(const FString& Actual, + const FString& Unexpected, + bool bIgnoreCase, + const FString& What, + UObject* Context /*= nullptr*/) +{ + ESearchCase::Type SearchCase = bIgnoreCase ? ESearchCase::IgnoreCase + : ESearchCase::CaseSensitive; + + if (Actual.Equals(Unexpected, SearchCase)) + { + FString Message = FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualText(const FText& Actual, + const FText& Expected, + bool bIgnoreCase, const FString& What, + UObject* Context /*= nullptr*/) +{ + bool bEquals = bIgnoreCase ? Actual.EqualToCaseIgnored(Expected) : Actual.EqualTo(Expected); + + if (!bEquals) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, + {What, Expected.ToString(), Actual.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualText(const FText& Actual, + const FText& Unexpected, + bool bIgnoreCase, + const FString& What, + UObject* Context /*= nullptr*/) +{ + bool bEquals = bIgnoreCase ? Actual.EqualToCaseIgnored(Unexpected) : Actual.EqualTo(Unexpected); + + if (bEquals) + { + FString Message = + FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualVector(const FVector& Actual, + const FVector& Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!Actual.Equals(Expected)) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, + {What, Expected.ToString(), Actual.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualVector(const FVector& Actual, + const FVector& Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (Actual.Equals(Unexpected)) + { + FString Message = + FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualRotator(const FRotator& Actual, + const FRotator& Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!Actual.Equals(Expected)) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, + {What, Expected.ToString(), Actual.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualRotator(const FRotator& Actual, + const FRotator& Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (Actual.Equals(Unexpected)) + { + FString Message = + FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertEqualTransform(const FTransform& Actual, + const FTransform& Expected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!Actual.Equals(Expected)) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, + {What, Expected.ToString(), Actual.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotEqualTransform(const FTransform& Actual, + const FTransform& Unexpected, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (Actual.Equals(Unexpected)) + { + FString Message = + FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected.ToString()}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertInRangeByte(uint8 Value, uint8 MinInclusive, + uint8 MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertInRangeInt32(Value, MinInclusive, MaxInclusive, What, Context); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotInRangeByte(uint8 Value, uint8 MinInclusive, + uint8 MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + AssertNotInRangeInt32(Value, MinInclusive, MaxInclusive, What, Context); +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertInRangeInt32(int32 Value, int32 MinInclusive, + int32 MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!UKismetMathLibrary::InRange_IntInt(Value, MinInclusive, MaxInclusive)) + { + FString Message = + FString::Format(*ErrorMessageFormatInRange, {What, MinInclusive, MaxInclusive, Value}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotInRangeInt32(int32 Value, int32 MinInclusive, + int32 MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (UKismetMathLibrary::InRange_IntInt(Value, MinInclusive, MaxInclusive)) + { + FString Message = FString::Format(*ErrorMessageFormatNotInRange, + {What, MinInclusive, MaxInclusive, Value}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertInRangeInt64(int64 Value, int64 MinInclusive, + int64 MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!UKismetMathLibrary::InRange_Int64Int64(Value, MinInclusive, MaxInclusive)) + { + FString Message = + FString::Format(*ErrorMessageFormatInRange, {What, MinInclusive, MaxInclusive, Value}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotInRangeInt64(int64 Value, int64 MinInclusive, + int64 MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (UKismetMathLibrary::InRange_Int64Int64(Value, MinInclusive, MaxInclusive)) + { + FString Message = FString::Format(*ErrorMessageFormatNotInRange, + {What, MinInclusive, MaxInclusive, Value}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertInRangeFloat(float Value, float MinInclusive, + float MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!UKismetMathLibrary::InRange_FloatFloat(Value, MinInclusive, MaxInclusive)) + { + FString Message = + FString::Format(*ErrorMessageFormatInRange, {What, MinInclusive, MaxInclusive, Value}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertNotInRangeFloat(float Value, float MinInclusive, + float MaxInclusive, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (UKismetMathLibrary::InRange_FloatFloat(Value, MinInclusive, MaxInclusive)) + { + FString Message = FString::Format(*ErrorMessageFormatNotInRange, + {What, MinInclusive, MaxInclusive, Value}); + OnTestFailed(Context, Message); + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertWidgetIsVisible(UUserWidget* Widget, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!IsValid(Widget)) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Widget is not valid"), {What}); + OnTestFailed(Context, Message); + return; + } + + if (!Widget->IsInViewport() && !IsValid(Widget->GetParent())) + { + FString Message = FString::Format( + TEXT("Assertion failed - {0} - Widget hasn't been added to the viewport"), {What}); + OnTestFailed(Context, Message); + return; + } + + if (!Widget->IsVisible()) + { + FString Message = FString::Format(TEXT("Assertion failed - {0} - Widget isn't visible, hit " + "test visible or self hit test visible"), + {What}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertTextIsSet(UTextBlock* TextBlock, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!IsValid(TextBlock)) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Text block is not valid"), {What}); + OnTestFailed(Context, Message); + return; + } + + if (TextBlock->GetText().IsEmpty()) + { + FString Message = FString::Format(TEXT("Assertion failed - {0} - Text is empty"), {What}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertRichTextIsSet(URichTextBlock* RichTextBlock, + const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!IsValid(RichTextBlock)) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Rich text block is not valid"), {What}); + OnTestFailed(Context, Message); + return; + } + + if (RichTextBlock->GetText().IsEmpty()) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Rich text is empty"), {What}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::AssertImageIsSet(UImage* Image, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!IsValid(Image)) + { + FString Message = + FString::Format(TEXT("Assertion failed - {0} - Image is not valid"), {What}); + OnTestFailed(Context, Message); + return; + } + + if (!IsValid(Image->Brush.GetResourceObject())) + { + FString Message = FString::Format(TEXT("Assertion failed - {0} - Image brush has no " + "resource object (e.g. texture or material)"), + {What}); + OnTestFailed(Context, Message); + return; + } +} + +void UDaeTestAssertBlueprintFunctionLibrary::OnTestFailed(UObject* Context, const FString& Message) +{ + ADaeTestActor* TestActor = Cast(Context); + + if (IsValid(TestActor)) + { + TestActor->NotifyOnTestFailed(Message); + } + else + { + UE_LOG(LogDaeTest, Error, TEXT("%s"), *Message); + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAssumeBlueprintFunctionLibrary.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAssumeBlueprintFunctionLibrary.cpp new file mode 100644 index 00000000..bbf2cfbc --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAssumeBlueprintFunctionLibrary.cpp @@ -0,0 +1,41 @@ +#include "DaeTestAssumeBlueprintFunctionLibrary.h" +#include "DaeTestActor.h" +#include "DaeTestLogCategory.h" + +void UDaeTestAssumeBlueprintFunctionLibrary::AssumeTrue(bool bValue, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (!bValue) + { + FString Message = + FString::Format(TEXT("Assumption failed - {0} - Expected: True, but was: False"), + {What}); + OnTestSkipped(Context, Message); + } +} + +void UDaeTestAssumeBlueprintFunctionLibrary::AssumeFalse(bool bValue, const FString& What, + UObject* Context /*= nullptr*/) +{ + if (bValue) + { + FString Message = + FString::Format(TEXT("Assumption failed - {0} - Expected: False, but was: True"), + {What}); + OnTestSkipped(Context, Message); + } +} + +void UDaeTestAssumeBlueprintFunctionLibrary::OnTestSkipped(UObject* Context, const FString& Message) +{ + ADaeTestActor* TestActor = Cast(Context); + + if (IsValid(TestActor)) + { + TestActor->NotifyOnTestSkipped(Message); + } + else + { + UE_LOG(LogDaeTest, Log, TEXT("%s"), *Message); + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAutomationPluginSettings.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAutomationPluginSettings.cpp new file mode 100644 index 00000000..75456011 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestAutomationPluginSettings.cpp @@ -0,0 +1,17 @@ +#include "DaeTestAutomationPluginSettings.h" + +UDaeTestAutomationPluginSettings::UDaeTestAutomationPluginSettings() + : TestMapPath(TEXT("Maps/AutomatedTests")) +{ +} + +#if WITH_EDITOR +void UDaeTestAutomationPluginSettings::PostEditChangeProperty( + struct FPropertyChangedEvent& PropertyChangedEvent) +{ + if (PropertyChangedEvent.GetPropertyName() == TEXT("TestMapPath")) + { + OnTestMapPathChanged.Broadcast(TestMapPath); + } +} +#endif diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestBlueprintFunctionLibrary.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestBlueprintFunctionLibrary.cpp new file mode 100644 index 00000000..e0cc27cc --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestBlueprintFunctionLibrary.cpp @@ -0,0 +1,43 @@ +#include "DaeTestBlueprintFunctionLibrary.h" +#include "DaeDelayFramesAction.h" +#include "DaeDelayUntilTriggeredAction.h" +#include +#include +#include + +void UDaeTestBlueprintFunctionLibrary::DelayFrames(UObject* WorldContextObject, + struct FLatentActionInfo LatentInfo, + int32 NumFrames) +{ + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, + EGetWorldErrorMode::LogAndReturnNull)) + { + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + if (LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, + LatentInfo.UUID) + == NULL) + { + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, + new FDaeDelayFramesAction(LatentInfo, NumFrames)); + } + } +} + +void UDaeTestBlueprintFunctionLibrary::DelayUntilTriggered(UObject* WorldContextObject, + struct FLatentActionInfo LatentInfo, + ADaeTestTriggerBox* TestTriggerBox) +{ + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, + EGetWorldErrorMode::LogAndReturnNull)) + { + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + if (LatentActionManager.FindExistingAction( + LatentInfo.CallbackTarget, LatentInfo.UUID) + == NULL) + { + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, + new FDaeDelayUntilTriggeredAction(LatentInfo, + TestTriggerBox)); + } + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestDelayBlueprintFunctionLibrary.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestDelayBlueprintFunctionLibrary.cpp new file mode 100644 index 00000000..68d7d58a --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestDelayBlueprintFunctionLibrary.cpp @@ -0,0 +1,43 @@ +#include "DaeTestDelayBlueprintFunctionLibrary.h" +#include "DaeDelayFramesAction.h" +#include "DaeDelayUntilTriggeredAction.h" +#include +#include +#include + +void UDaeTestDelayBlueprintFunctionLibrary::DelayFrames(UObject* WorldContextObject, + struct FLatentActionInfo LatentInfo, + int32 NumFrames) +{ + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, + EGetWorldErrorMode::LogAndReturnNull)) + { + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + if (LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, + LatentInfo.UUID) + == NULL) + { + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, + new FDaeDelayFramesAction(LatentInfo, NumFrames)); + } + } +} + +void UDaeTestDelayBlueprintFunctionLibrary::DelayUntilTriggered(UObject* WorldContextObject, + struct FLatentActionInfo LatentInfo, + ADaeTestTriggerBox* TestTriggerBox) +{ + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, + EGetWorldErrorMode::LogAndReturnNull)) + { + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + if (LatentActionManager.FindExistingAction( + LatentInfo.CallbackTarget, LatentInfo.UUID) + == NULL) + { + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, + new FDaeDelayUntilTriggeredAction(LatentInfo, + TestTriggerBox)); + } + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestInputBlueprintFunctionLibrary.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestInputBlueprintFunctionLibrary.cpp new file mode 100644 index 00000000..15f1ea0b --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestInputBlueprintFunctionLibrary.cpp @@ -0,0 +1,48 @@ +#include "DaeTestInputBlueprintFunctionLibrary.h" +#include "DaeTestLogCategory.h" +#include +#include +#include +#include +#include + +void UDaeTestInputBlueprintFunctionLibrary::ApplyInputAction( + UObject* Context, const FName& ActionName, + EInputEvent InputEventType /*= EInputEvent::IE_Pressed*/) +{ + APlayerController* PlayerController = UGameplayStatics::GetPlayerController(Context, 0); + + const UInputSettings* InputSettings = GetDefault(); + + for (const FInputActionKeyMapping& Mapping : InputSettings->GetActionMappings()) + { + if (Mapping.ActionName == ActionName) + { + PlayerController->InputKey(Mapping.Key, InputEventType, 0.0f, false); + return; + } + } + + UE_LOG(LogDaeTest, Error, TEXT("%s - Input action not found: %s"), + IsValid(Context) ? *Context->GetName() : TEXT(""), *ActionName.ToString()); +} + +void UDaeTestInputBlueprintFunctionLibrary::ApplyInputAxis(UObject* Context, const FName& AxisName, + float AxisValue /*= 1.0f*/) +{ + APlayerController* PlayerController = UGameplayStatics::GetPlayerController(Context, 0); + + const UInputSettings* InputSettings = GetDefault(); + + for (const FInputAxisKeyMapping& Mapping : InputSettings->GetAxisMappings()) + { + if (Mapping.AxisName == AxisName) + { + PlayerController->InputAxis(Mapping.Key, AxisValue, 0.0f, 1, false); + return; + } + } + + UE_LOG(LogDaeTest, Error, TEXT("%s - Input axis not found: %s"), + IsValid(Context) ? *Context->GetName() : TEXT(""), *AxisName.ToString()); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestLogCategory.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestLogCategory.cpp new file mode 100644 index 00000000..25e33afc --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestLogCategory.cpp @@ -0,0 +1,3 @@ +#include "DaeTestLogCategory.h" + +DEFINE_LOG_CATEGORY(LogDaeTest); diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestParameterProviderActor.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestParameterProviderActor.cpp new file mode 100644 index 00000000..8060c91e --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestParameterProviderActor.cpp @@ -0,0 +1,6 @@ +#include "DaeTestParameterProviderActor.h" + +TArray ADaeTestParameterProviderActor::GetParameters_Implementation() +{ + return TArray(); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestParameterProviderActorBlueprint.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestParameterProviderActorBlueprint.cpp new file mode 100644 index 00000000..c485205b --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestParameterProviderActorBlueprint.cpp @@ -0,0 +1 @@ +#include "DaeTestParameterProviderActorBlueprint.h" diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestResult.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestResult.cpp new file mode 100644 index 00000000..ba6a4c86 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestResult.cpp @@ -0,0 +1,27 @@ +#include "DaeTestResult.h" + +FDaeTestResult::FDaeTestResult() + : FDaeTestResult(FString(), 0.0f) +{ +} + +FDaeTestResult::FDaeTestResult(FString InTestName, float InTimeSeconds) + : TestName(InTestName) + , TimeSeconds(InTimeSeconds) +{ +} + +bool FDaeTestResult::WasSuccessful() const +{ + return !HasFailed() && !WasSkipped(); +} + +bool FDaeTestResult::HasFailed() const +{ + return !FailureMessage.IsEmpty(); +} + +bool FDaeTestResult::WasSkipped() const +{ + return !SkipReason.IsEmpty(); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteActor.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteActor.cpp new file mode 100644 index 00000000..d751e2f6 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteActor.cpp @@ -0,0 +1,307 @@ +#include "DaeTestSuiteActor.h" +#include "DaeTestActor.h" +#include "DaeTestLogCategory.h" +#include + +ADaeTestSuiteActor::ADaeTestSuiteActor( + const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) +{ + bRunInPIE = true; + TestIndex = -1; + + PrimaryActorTick.bCanEverTick = true; + + // We need to be able to time out even while gameplay is paused. + PrimaryActorTick.bTickEvenWhenPaused = true; +} + +void ADaeTestSuiteActor::BeginPlay() +{ + Super::BeginPlay(); + + // Setup result data. + Result.MapName = GetWorld()->GetMapName(); + Result.TestSuiteName = GetName(); + Result.Timestamp = FDateTime::UtcNow(); +} + +void ADaeTestSuiteActor::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + if (!IsRunning()) + { + // Check if we should run all tests immediately. + // Happening in first Tick to make sure all actors have begun play. + if (bRunInPIE && GetWorld()->IsPlayInEditor() && TestIndex < 0) + { + RunAllTests(); + } + + return; + } + + TestTimeSeconds += DeltaSeconds; + + ADaeTestActor* CurrentTest = GetCurrentTest(); + + if (TestTimeSeconds >= CurrentTest->GetTimeoutInSeconds()) + { + // Enough waiting. Let's see the results. + UE_LOG(LogDaeTest, Warning, TEXT("Timed out after %f seconds"), + CurrentTest->GetTimeoutInSeconds()); + + CurrentTest->FinishAct(); + } +} + +void ADaeTestSuiteActor::RunAllTests() +{ + UE_LOG(LogDaeTest, Display, TEXT("ADaeTestSuiteActor::RunAllTests - Test Suite: %s"), + *GetName()); + + NotifyOnBeforeAll(); + + TestIndex = -1; + TestParameterIndex = -1; + + RunNextTest(); +} + +bool ADaeTestSuiteActor::IsRunning() const +{ + return IsValid(GetCurrentTest()); +} + +ADaeTestActor* ADaeTestSuiteActor::GetCurrentTest() const +{ + return Tests.IsValidIndex(TestIndex) ? Tests[TestIndex] : nullptr; +} + +UObject* ADaeTestSuiteActor::GetCurrentTestParameter() const +{ + ADaeTestActor* Test = GetCurrentTest(); + + if (!IsValid(Test)) + { + return nullptr; + } + + TArray> TestParameters = Test->GetParameters(); + + if (!TestParameters.IsValidIndex(TestParameterIndex)) + { + return nullptr; + } + + TSoftObjectPtr Parameter = TestParameters[TestParameterIndex]; + + if (!Parameter.IsValid()) + { + return nullptr; + } + + return Parameter.LoadSynchronous(); +} + +FString ADaeTestSuiteActor::GetCurrentTestName() const +{ + ADaeTestActor* Test = GetCurrentTest(); + + if (!IsValid(Test)) + { + return FString(); + } + + FString TestName = Test->GetName(); + + UObject* Parameter = GetCurrentTestParameter(); + + if (IsValid(Parameter)) + { + TestName += TEXT(" - ") + Parameter->GetName(); + } + + return TestName; +} + +FDaeTestSuiteResult ADaeTestSuiteActor::GetResult() const +{ + return Result; +} + +void ADaeTestSuiteActor::NotifyOnBeforeAll() +{ + ReceiveOnBeforeAll(); +} + +void ADaeTestSuiteActor::NotifyOnAfterAll() +{ + ReceiveOnAfterAll(); +} + +void ADaeTestSuiteActor::NotifyOnBeforeEach() +{ + ReceiveOnBeforeEach(); +} + +void ADaeTestSuiteActor::NotifyOnAfterEach() +{ + ReceiveOnAfterEach(); +} + +void ADaeTestSuiteActor::RunNextTest() +{ + ADaeTestActor* CurrentTest = GetCurrentTest(); + + // Unregister events. + if (IsValid(CurrentTest)) + { + CurrentTest->OnTestSuccessful.RemoveDynamic(this, &ADaeTestSuiteActor::OnTestSuccessful); + CurrentTest->OnTestFailed.RemoveDynamic(this, &ADaeTestSuiteActor::OnTestFailed); + CurrentTest->OnTestSkipped.RemoveDynamic(this, &ADaeTestSuiteActor::OnTestSkipped); + } + + // Prepare test run with next parameter. + ++TestParameterIndex; + + UObject* CurrentTestParameter = GetCurrentTestParameter(); + + if (!IsValid(CurrentTestParameter)) + { + // Prepare next test. + ++TestIndex; + TestParameterIndex = 0; + + // Apply parameter providers. + ADaeTestActor* NextTest = GetCurrentTest(); + + if (IsValid(NextTest)) + { + NextTest->ApplyParameterProviders(); + } + } + + TestTimeSeconds = 0.0f; + + if (!Tests.IsValidIndex(TestIndex)) + { + // All tests finished. + UE_LOG(LogDaeTest, Display, TEXT("ADaeTestSuiteActor::RunNextTest - All tests finished.")); + + NotifyOnAfterAll(); + + // Check if any test failed. + for (const FDaeTestResult& TestResult : Result.TestResults) + { + if (!TestResult.FailureMessage.IsEmpty()) + { + OnTestSuiteFailed.Broadcast(this); + return; + } + } + + OnTestSuiteSuccessful.Broadcast(this); + return; + } + + ADaeTestActor* Test = GetCurrentTest(); + + if (IsValid(Test)) + { + FString TestName = GetCurrentTestName(); + UE_LOG(LogDaeTest, Display, TEXT("ADaeTestSuiteActor::RunNextTest - Test: %s"), *TestName); + + // Register events. + Test->OnTestSuccessful.AddDynamic(this, &ADaeTestSuiteActor::OnTestSuccessful); + Test->OnTestFailed.AddDynamic(this, &ADaeTestSuiteActor::OnTestFailed); + Test->OnTestSkipped.AddDynamic(this, &ADaeTestSuiteActor::OnTestSkipped); + + // Run test. + NotifyOnBeforeEach(); + + UObject* TestParameter = GetCurrentTestParameter(); + Test->RunTest(TestParameter); + } + else + { + UE_LOG(LogDaeTest, Error, + TEXT("ADaeTestSuiteActor::RunNextTest - %s has invalid test at index %i, skipping."), + *GetName(), TestIndex); + + RunNextTest(); + } +} + +void ADaeTestSuiteActor::OnTestSuccessful(ADaeTestActor* Test, UObject* Parameter) +{ + if (Test != GetCurrentTest()) + { + // Prevent tests from reporting multiple results. + return; + } + + FString CurrentTestName = GetCurrentTestName(); + + UE_LOG(LogDaeTest, Display, TEXT("ADaeTestSuiteActor::OnTestSuccessful - Test: %s"), + *CurrentTestName); + + // Store result. + FDaeTestResult TestResult(CurrentTestName, TestTimeSeconds); + Result.TestResults.Add(TestResult); + + // Run next test. + NotifyOnAfterEach(); + + RunNextTest(); +} + +void ADaeTestSuiteActor::OnTestFailed(ADaeTestActor* Test, UObject* Parameter, + const FString& FailureMessage) +{ + if (Test != GetCurrentTest()) + { + // Prevent tests from reporting multiple results. + return; + } + + FString CurrentTestName = GetCurrentTestName(); + + UE_LOG(LogDaeTest, Error, + TEXT("ADaeTestSuiteActor::OnTestFailed - Test: %s, FailureMessage: %s"), + *CurrentTestName, *FailureMessage); + + // Store result. + FDaeTestResult TestResult(CurrentTestName, TestTimeSeconds); + TestResult.FailureMessage = FailureMessage; + Result.TestResults.Add(TestResult); + + // Run next test. + NotifyOnAfterEach(); + + RunNextTest(); +} + +void ADaeTestSuiteActor::OnTestSkipped(ADaeTestActor* Test, UObject* Parameter, + const FString& SkipReason) +{ + if (Test != GetCurrentTest()) + { + // Prevent tests from reporting multiple results. + return; + } + + FString CurrentTestName = GetCurrentTestName(); + + UE_LOG(LogDaeTest, Display, + TEXT("ADaeTestSuiteActor::OnTestSkipped - Test: %s, SkipReason: %s"), *CurrentTestName, + *SkipReason); + + // Store result. + FDaeTestResult TestResult(CurrentTestName, TestTimeSeconds); + TestResult.SkipReason = SkipReason; + Result.TestResults.Add(TestResult); + + // Run next test. + RunNextTest(); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteActorBlueprint.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteActorBlueprint.cpp new file mode 100644 index 00000000..4356f7cd --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteActorBlueprint.cpp @@ -0,0 +1 @@ +#include "DaeTestSuiteActorBlueprint.h" diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteResult.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteResult.cpp new file mode 100644 index 00000000..5ce15b75 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestSuiteResult.cpp @@ -0,0 +1,53 @@ +#include "DaeTestSuiteResult.h" + +int32 FDaeTestSuiteResult::NumTotalTests() const +{ + return TestResults.Num(); +} + +int32 FDaeTestSuiteResult::NumSuccessfulTests() const +{ + return NumTotalTests() - NumFailedTests() - NumSkippedTests(); +} + +int32 FDaeTestSuiteResult::NumFailedTests() const +{ + int32 FailedTests = 0; + + for (const FDaeTestResult& TestResult : TestResults) + { + if (TestResult.HasFailed()) + { + ++FailedTests; + } + } + + return FailedTests; +} + +int32 FDaeTestSuiteResult::NumSkippedTests() const +{ + int32 SkippedTests = 0; + + for (const FDaeTestResult& TestResult : TestResults) + { + if (TestResult.WasSkipped()) + { + ++SkippedTests; + } + } + + return SkippedTests; +} + +float FDaeTestSuiteResult::GetTotalTimeSeconds() const +{ + float TimeSeconds = 0.0f; + + for (const FDaeTestResult& TestResult : TestResults) + { + TimeSeconds += TestResult.TimeSeconds; + } + + return TimeSeconds; +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestTriggerBox.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestTriggerBox.cpp new file mode 100644 index 00000000..cc31290d --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaeTestTriggerBox.cpp @@ -0,0 +1,23 @@ +#include "DaeTestTriggerBox.h" +#include "DaeTestLogCategory.h" + +void ADaeTestTriggerBox::BeginPlay() +{ + Super::BeginPlay(); + + bWasTriggered = false; + + OnActorBeginOverlap.AddDynamic(this, &ADaeTestTriggerBox::OnActorBeginOverlapBroadcast); +} + +bool ADaeTestTriggerBox::WasTriggered() const +{ + return bWasTriggered; +} + +void ADaeTestTriggerBox::OnActorBeginOverlapBroadcast(AActor* OverlappedActor, AActor* OtherActor) +{ + bWasTriggered = true; + + UE_LOG(LogDaeTest, Log, TEXT("%s was triggered by %s."), *GetName(), *OtherActor->GetName()); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaedalicTestAutomationPlugin.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaedalicTestAutomationPlugin.cpp new file mode 100644 index 00000000..12229257 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Private/DaedalicTestAutomationPlugin.cpp @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" +#include "IDaedalicTestAutomationPlugin.h" + + +class FDaedalicTestAutomationPlugin : public IDaedalicTestAutomationPlugin +{ + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; + +IMPLEMENT_MODULE( FDaedalicTestAutomationPlugin, DaedalicTestAutomationPlugin ) + + + +void FDaedalicTestAutomationPlugin::StartupModule() +{ + // This code will execute after your module is loaded into memory (but after global variables are initialized, of course.) +} + + +void FDaedalicTestAutomationPlugin::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + + + diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeDelayFramesAction.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeDelayFramesAction.h new file mode 100644 index 00000000..065b5486 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeDelayFramesAction.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +/** Triggers the output link after the specified number of frames. */ +class DAEDALICTESTAUTOMATIONPLUGIN_API FDaeDelayFramesAction : public FPendingLatentAction +{ +public: + int32 FramesRemaining; + FName ExecutionFunction; + int32 OutputLink; + FWeakObjectPtr CallbackTarget; + + FDaeDelayFramesAction(const FLatentActionInfo& LatentInfo, int32 NumFrames); + + virtual void UpdateOperation(FLatentResponse& Response) override; + +#if WITH_EDITOR + virtual FString GetDescription() const override; +#endif +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeDelayUntilTriggeredAction.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeDelayUntilTriggeredAction.h new file mode 100644 index 00000000..fd056340 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeDelayUntilTriggeredAction.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +class ADaeTestTriggerBox; + +/** Triggers the output link after the specified trigger box has been triggered. */ +class DAEDALICTESTAUTOMATIONPLUGIN_API FDaeDelayUntilTriggeredAction : public FPendingLatentAction +{ +public: + ADaeTestTriggerBox* TestTriggerBox; + FName ExecutionFunction; + int32 OutputLink; + FWeakObjectPtr CallbackTarget; + + FDaeDelayUntilTriggeredAction(const FLatentActionInfo& LatentInfo, + ADaeTestTriggerBox* InTestTriggerBox); + + virtual void UpdateOperation(FLatentResponse& Response) override; + +#if WITH_EDITOR + virtual FString GetDescription() const override; +#endif +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeGauntletStates.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeGauntletStates.h new file mode 100644 index 00000000..3245dd41 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeGauntletStates.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +struct DAEDALICTESTAUTOMATIONPLUGIN_API FDaeGauntletStates : FGauntletStates +{ + static FName LoadingNextMap; + static FName DiscoveringTests; + static FName Running; + static FName Finished; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeGauntletTestController.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeGauntletTestController.h new file mode 100644 index 00000000..91fc386b --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeGauntletTestController.h @@ -0,0 +1,32 @@ +#pragma once + +#include "DaeTestSuiteResult.h" +#include +#include +#include "DaeGauntletTestController.generated.h" + +class ADaeTestSuiteActor; + +/** Controller for automated tests run by Gauntlet. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeGauntletTestController : public UGauntletTestController +{ + GENERATED_BODY() + +public: + virtual void OnInit() override; + virtual void OnPostMapChange(UWorld* World) override; + virtual void OnTick(float TimeDelta) override; + +private: + TArray MapNames; + int32 MapIndex; + TArray Results; + + void LoadNextTestMap(); + + UFUNCTION() + void OnTestSuiteFinished(ADaeTestSuiteActor* TestSuite); + + FString ParseCommandLineOption(const FString& Key); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeJUnitReportWriter.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeJUnitReportWriter.h new file mode 100644 index 00000000..4000997f --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeJUnitReportWriter.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "DaeTestSuiteResult.h" + +/** Writes test reports based on the Apache Ant JUnit report format (based on org.junit.platform.reporting.legacy.xml.XmlReportWriter.writeTestsuite). */ +class DAEDALICTESTAUTOMATIONPLUGIN_API FDaeJUnitReportWriter +{ +public: + /** Returns an XML report based on the Apache Ant JUnit report format. */ + FString CreateReport(const FString& Name, const TArray& TestSuites) const; + +private: + int32 NumTotalTests(const TArray& TestSuites) const; + int32 NumFailedTests(const TArray& TestSuites) const; + int32 NumSkippedTests(const TArray& TestSuites) const; + float GetTotalTimeSeconds(const TArray& TestSuites) const; + FString GetTimestamp(const TArray& TestSuites) const; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestActor.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestActor.h new file mode 100644 index 00000000..5451c1b6 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestActor.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include "DaeTestActor.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDaeTestActorTestSuccessfulSignature, ADaeTestActor*, + Test, UObject*, Parameter); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FDaeTestActorTestFailedSignature, ADaeTestActor*, + Test, UObject*, Parameter, const FString&, + FailureMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FDaeTestActorTestSkippedSignature, ADaeTestActor*, + Test, UObject*, Parameter, const FString&, + SkipReason); + +class ADaeTestParameterProviderActor; + +/** Single automated test to be run as part of a test suite. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API ADaeTestActor : public AActor +{ + GENERATED_BODY() + +public: + ADaeTestActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Applies additional providers for appending parameters for this test. */ + void ApplyParameterProviders(); + + /** Starts executing this test. */ + void RunTest(UObject* TestParameter); + + /** Finishes execution of this test, automatically following up with the Assert step. */ + UFUNCTION(BlueprintCallable) + void FinishAct(); + + /** Gets how long this test is allowed to run before it fails automatically, in seconds. */ + float GetTimeoutInSeconds() const; + + /** Gets the parameters to run this test with, one per run. */ + TArray> GetParameters() const; + + /** Gets the parameter for the current test run. */ + UFUNCTION(BlueprintPure) + UObject* GetCurrentParameter() const; + + /** Event when this test has finished successfully. */ + virtual void NotifyOnTestSuccessful(); + + /** Event when this test has failed. */ + virtual void NotifyOnTestFailed(const FString& Message); + + /** Event when this test has been skipped. */ + virtual void NotifyOnTestSkipped(const FString& InSkipReason); + + /** Event when this test should verify its preconditions. */ + virtual void NotifyOnAssume(UObject* Parameter); + + /** Event when this test should set up. */ + virtual void NotifyOnArrange(UObject* Parameter); + + /** Event when this test should execute. */ + virtual void NotifyOnAct(UObject* Parameter); + + /** Event when this test should check the results. */ + virtual void NotifyOnAssert(UObject* Parameter); + + /** Event when this test should verify its preconditions. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Assume")) + void ReceiveOnAssume(UObject* Parameter); + + /** Event when this test should set up. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Arrange")) + void ReceiveOnArrange(UObject* Parameter); + + /** Event when this test should execute. This is a latent event: You need to call FinishAct when you're finished. */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "Act")) + void ReceiveOnAct(UObject* Parameter); + + /** Event when this test should check the results. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Assert")) + void ReceiveOnAssert(UObject* Parameter); + + /** Event when this test has finished successfully. */ + FDaeTestActorTestSuccessfulSignature OnTestSuccessful; + + /** Event when this test has failed. */ + FDaeTestActorTestFailedSignature OnTestFailed; + + /** Event when this test has been skipped. */ + FDaeTestActorTestSkippedSignature OnTestSkipped; + +private: + /** How long this test is allowed to run before it fails automatically, in seconds. */ + UPROPERTY(EditAnywhere) + float TimeoutInSeconds; + + /** Reason for skipping this test. Test will be skipped if this is not empty. Useful for temporarily disabling unstable tests. */ + UPROPERTY(EditAnywhere) + FString SkipReason; + + /** Parameterizes this test, running it multiple times, once per specified parameter. */ + UPROPERTY(EditAnywhere) + TArray> Parameters; + + /** Additional providers for appending parameters for this test. Applied exactly once before the first test run. */ + UPROPERTY(EditAnywhere) + TArray ParameterProviders; + + /** Parameter for the current test run. */ + UPROPERTY() + UObject* CurrentParameter; + + /** Whether this test has finished executing (either with success or failure). */ + bool bHasResult; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestActorBlueprint.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestActorBlueprint.h new file mode 100644 index 00000000..1ef59528 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestActorBlueprint.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "DaeTestActorBlueprint.generated.h" + +/** Single automated test to be run as part of a test suite. */ +UCLASS(BlueprintType) +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestActorBlueprint : public UBlueprint +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + // UBlueprint interface + virtual bool SupportedByDefaultBlueprintFactory() const override + { + return false; + } + // End of UBlueprint interface +#endif +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAssertBlueprintFunctionLibrary.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAssertBlueprintFunctionLibrary.h new file mode 100644 index 00000000..465ba5f8 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAssertBlueprintFunctionLibrary.h @@ -0,0 +1,351 @@ +#pragma once + +#include "DaeTestComparisonMethod.h" +#include +#include +#include +#include "DaeTestAssertBlueprintFunctionLibrary.generated.h" + +class UImage; +class URichTextBlock; +class UTextBlock; +class UUserWidget; + +class ADaeTestTriggerBox; + +/** Utility functions for asserting state in automated tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestAssertBlueprintFunctionLibrary + : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Finishes the current test as failure. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertFail(const FString& What, UObject* Context = nullptr); + + /** Expects the specified value to be true. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertTrue(bool bValue, const FString& What, UObject* Context = nullptr); + + /** Expects the specified value to be false. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertFalse(bool bValue, const FString& What, UObject* Context = nullptr); + + /** Expects the specified object to be valid. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertValid(UObject* Object, const FString& What, UObject* Context = nullptr); + + /** Expects the specified object not to be valid. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertInvalid(UObject* Object, const FString& What, UObject* Context = nullptr); + + /** Expects the specified trigger box to be triggered. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertWasTriggered(ADaeTestTriggerBox* TestTriggerBox, UObject* Context = nullptr); + + /** Expects the specified trigger box not to be triggered. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertWasNotTriggered(ADaeTestTriggerBox* TestTriggerBox, + UObject* Context = nullptr); + + /** Expects the specified bytes to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Byte)")) + static void AssertEqualByte(uint8 Actual, uint8 Expected, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified bytes not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Byte)")) + static void AssertNotEqualByte(uint8 Actual, uint8 Unexpected, const FString& What, + UObject* Context = nullptr); + + /** Compares the specified bytes for order. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Compare (Byte)")) + static void AssertCompareByte(uint8 First, EDaeTestComparisonMethod ShouldBe, uint8 Second, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified 32-bit integers to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Integer)")) + static void AssertEqualInt32(int32 Actual, int32 Expected, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified 32-bit integers not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Integer)")) + static void AssertNotEqualInt32(int32 Actual, int32 Unexpected, const FString& What, + UObject* Context = nullptr); + + /** Compares the specified 32-bit integers for order. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Compare (Integer)")) + static void AssertCompareInt32(int32 First, EDaeTestComparisonMethod ShouldBe, int32 Second, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified 64-bit integers to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Integer64)")) + static void AssertEqualInt64(int64 Actual, int64 Expected, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified 64-bit integers not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Integer64)")) + static void AssertNotEqualInt64(int64 Actual, int64 Unexpected, const FString& What, + UObject* Context = nullptr); + + /** Compares the specified 64-bit integers for order. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Compare (Integer64)")) + static void AssertCompareInt64(int64 First, EDaeTestComparisonMethod ShouldBe, int64 Second, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified floats to be (nearly) equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Float)")) + static void AssertEqualFloat(float Actual, float Expected, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified floats not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Float)")) + static void AssertNotEqualFloat(float Actual, float Unexpected, const FString& What, + UObject* Context = nullptr); + + /** Compares the specified floats for order. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Compare (Float)")) + static void AssertCompareFloat(float First, EDaeTestComparisonMethod ShouldBe, float Second, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified names to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Name)")) + static void AssertEqualName(const FName& Actual, const FName& Expected, bool bIgnoreCase, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified names not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Name)")) + static void AssertNotEqualName(const FName& Actual, const FName& Unexpected, bool bIgnoreCase, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified strings to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (String)")) + static void AssertEqualString(const FString& Actual, const FString& Expected, bool bIgnoreCase, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified strings not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (String)")) + static void AssertNotEqualString(const FString& Actual, const FString& Unexpected, + bool bIgnoreCase, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified texts to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Text)")) + static void AssertEqualText(const FText& Actual, const FText& Expected, bool bIgnoreCase, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified texts not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Text)")) + static void AssertNotEqualText(const FText& Actual, const FText& Unexpected, bool bIgnoreCase, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified vectors to be (nearly) equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Vector)")) + static void AssertEqualVector(const FVector& Actual, const FVector& Expected, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified vectors not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Vector)")) + static void AssertNotEqualVector(const FVector& Actual, const FVector& Unexpected, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified rotators to be (nearly) equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Rotator)")) + static void AssertEqualRotator(const FRotator& Actual, const FRotator& Expected, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified rotators not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Rotator)")) + static void AssertNotEqualRotator(const FRotator& Actual, const FRotator& Unexpected, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified transforms to be (nearly) equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Equal (Transform)")) + static void AssertEqualTransform(const FTransform& Actual, const FTransform& Expected, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified transforms not to be equal. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not Equal (Transform)")) + static void AssertNotEqualTransform(const FTransform& Actual, const FTransform& Unexpected, + const FString& What, UObject* Context = nullptr); + + /** Expects Value to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert In Range (Byte)")) + static void AssertInRangeByte(uint8 Value, uint8 MinInclusive, uint8 MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value not to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not In Range (Byte)")) + static void AssertNotInRangeByte(uint8 Value, uint8 MinInclusive, uint8 MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert In Range (Integer)")) + static void AssertInRangeInt32(int32 Value, int32 MinInclusive, int32 MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value not to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not In Range (Integer)")) + static void AssertNotInRangeInt32(int32 Value, int32 MinInclusive, int32 MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert In Range (Integer64)")) + static void AssertInRangeInt64(int64 Value, int64 MinInclusive, int64 MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value not to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not In Range (Integer64)")) + static void AssertNotInRangeInt64(int64 Value, int64 MinInclusive, int64 MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert In Range (Float)")) + static void AssertInRangeFloat(float Value, float MinInclusive, float MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects Value not to be between MinInclusive and MaxInclusive. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context", + DisplayName = "Assert Not In Range (Float)")) + static void AssertNotInRangeFloat(float Value, float MinInclusive, float MaxInclusive, + const FString& What, UObject* Context = nullptr); + + /** Expects the specified widget to be valid and visible (e.g. added to viewport, not hidden or collapsed). */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertWidgetIsVisible(UUserWidget* Widget, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified text not to be empty. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertTextIsSet(UTextBlock* TextBlock, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified rich text not to be empty. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertRichTextIsSet(URichTextBlock* RichTextBlock, const FString& What, + UObject* Context = nullptr); + + /** Expects the specified image to be set up to use a texture or material. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssertImageIsSet(UImage* Image, const FString& What, UObject* Context = nullptr); + +private: + static const FString ErrorMessageFormatEqual; + static const FString ErrorMessageFormatNotEqual; + static const FString ErrorMessageFormatLessThan; + static const FString ErrorMessageFormatLessThanOrEqualTo; + static const FString ErrorMessageFormatGreaterThan; + static const FString ErrorMessageFormatGreaterThanOrEqualTo; + static const FString ErrorMessageFormatInRange; + static const FString ErrorMessageFormatNotInRange; + + static void OnTestFailed(UObject* Context, const FString& Message); + + template + static void AssertEqual(UObject* Context, const FString& What, T Actual, T Expected) + { + if (Actual != Expected) + { + FString Message = FString::Format(*ErrorMessageFormatEqual, {What, Expected, Actual}); + OnTestFailed(Context, Message); + } + } + + template + static void AssertNotEqual(UObject* Context, const FString& What, T Actual, T Unexpected) + { + if (Actual == Unexpected) + { + FString Message = FString::Format(*ErrorMessageFormatNotEqual, {What, Unexpected}); + OnTestFailed(Context, Message); + } + } + + template + static void AssertCompare(UObject* Context, const FString& What, T First, + EDaeTestComparisonMethod ShouldBe, T Second) + { + bool bFulfilled = false; + + switch (ShouldBe) + { + case EDaeTestComparisonMethod::LessThan: + bFulfilled = First < Second; + break; + + case EDaeTestComparisonMethod::LessThanOrEqualTo: + bFulfilled = First <= Second; + break; + + case EDaeTestComparisonMethod::GreaterThanOrEqualTo: + bFulfilled = First >= Second; + break; + + case EDaeTestComparisonMethod::GreaterThan: + bFulfilled = First > Second; + break; + } + + if (bFulfilled) + { + return; + } + + FString FormatString; + + switch (ShouldBe) + { + case EDaeTestComparisonMethod::LessThan: + FormatString = ErrorMessageFormatLessThan; + break; + + case EDaeTestComparisonMethod::LessThanOrEqualTo: + FormatString = ErrorMessageFormatLessThanOrEqualTo; + break; + + case EDaeTestComparisonMethod::GreaterThanOrEqualTo: + FormatString = ErrorMessageFormatGreaterThanOrEqualTo; + break; + + case EDaeTestComparisonMethod::GreaterThan: + FormatString = ErrorMessageFormatGreaterThan; + break; + } + + FString Message = FString::Format(*FormatString, {What, First, Second}); + OnTestFailed(Context, Message); + } +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAssumeBlueprintFunctionLibrary.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAssumeBlueprintFunctionLibrary.h new file mode 100644 index 00000000..89a50a0a --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAssumeBlueprintFunctionLibrary.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "DaeTestAssumeBlueprintFunctionLibrary.generated.h" + +/** Utility functions for assuming state before acting in automated tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestAssumeBlueprintFunctionLibrary + : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Expects the specified value to be true. Failed assumptions will cause automated tests to be skipped instead of failed. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssumeTrue(bool bValue, const FString& What, UObject* Context = nullptr); + + /** Expects the specified value to be true. Failed assumptions will cause automated tests to be skipped instead of failed. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void AssumeFalse(bool bValue, const FString& What, UObject* Context = nullptr); + +private: + static void OnTestSkipped(UObject* Context, const FString& Message); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAutomationPluginSettings.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAutomationPluginSettings.h new file mode 100644 index 00000000..7fea5612 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestAutomationPluginSettings.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include "DaeTestAutomationPluginSettings.generated.h" + +DECLARE_MULTICAST_DELEGATE_OneParam(FDaeTestAutomationPluginSettingsTestMapPathChangedSignature, + const FString&); + +/** Custom settings for this plugin. */ +UCLASS(config = Game, defaultconfig) +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestAutomationPluginSettings : public UObject +{ + GENERATED_BODY() + +public: + /** Path to look for test maps in. */ + UPROPERTY(config, EditAnywhere) + FString TestMapPath; + + UDaeTestAutomationPluginSettings(); + +#if WITH_EDITOR + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + /** Event when the path to look for test maps in has changed. */ + FDaeTestAutomationPluginSettingsTestMapPathChangedSignature OnTestMapPathChanged; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestBlueprintFunctionLibrary.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestBlueprintFunctionLibrary.h new file mode 100644 index 00000000..720882fa --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestBlueprintFunctionLibrary.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include "DaeTestBlueprintFunctionLibrary.generated.h" + +class ADaeTestTriggerBox; + +/** Utility functions for automating tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestBlueprintFunctionLibrary + : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Triggers the output link after the specified number of frames. */ + UFUNCTION(BlueprintCallable, + meta = (Latent, WorldContext = "WorldContextObject", LatentInfo = "LatentInfo")) + static void DelayFrames(UObject* WorldContextObject, struct FLatentActionInfo LatentInfo, + int32 NumFrames = 1); + + /** Triggers the output link after the specified trigger box has been triggered. */ + UFUNCTION(BlueprintCallable, + meta = (Latent, WorldContext = "WorldContextObject", LatentInfo = "LatentInfo")) + static void DelayUntilTriggered(UObject* WorldContextObject, + struct FLatentActionInfo LatentInfo, + ADaeTestTriggerBox* TestTriggerBox); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestComparisonMethod.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestComparisonMethod.h new file mode 100644 index 00000000..82afbbd6 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestComparisonMethod.h @@ -0,0 +1,12 @@ +#pragma once + +#include "DaeTestComparisonMethod.generated.h" + +UENUM(BlueprintType) +enum class EDaeTestComparisonMethod : uint8 +{ + LessThan, + LessThanOrEqualTo, + GreaterThanOrEqualTo, + GreaterThan +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestDelayBlueprintFunctionLibrary.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestDelayBlueprintFunctionLibrary.h new file mode 100644 index 00000000..1d1918aa --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestDelayBlueprintFunctionLibrary.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include "DaeTestDelayBlueprintFunctionLibrary.generated.h" + +class ADaeTestTriggerBox; + +/** Utility functions for sequencing actions in automated tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestDelayBlueprintFunctionLibrary + : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Triggers the output link after the specified number of frames. */ + UFUNCTION(BlueprintCallable, + meta = (Latent, WorldContext = "WorldContextObject", LatentInfo = "LatentInfo")) + static void DelayFrames(UObject* WorldContextObject, struct FLatentActionInfo LatentInfo, + int32 NumFrames = 1); + + /** Triggers the output link after the specified trigger box has been triggered. */ + UFUNCTION(BlueprintCallable, + meta = (Latent, WorldContext = "WorldContextObject", LatentInfo = "LatentInfo")) + static void DelayUntilTriggered(UObject* WorldContextObject, + struct FLatentActionInfo LatentInfo, + ADaeTestTriggerBox* TestTriggerBox); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestInputBlueprintFunctionLibrary.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestInputBlueprintFunctionLibrary.h new file mode 100644 index 00000000..559ed89f --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestInputBlueprintFunctionLibrary.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include "DaeTestInputBlueprintFunctionLibrary.generated.h" + +/** Utility functions for simulating input in automated tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestInputBlueprintFunctionLibrary + : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Applies the input action with the specified name once. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void ApplyInputAction(UObject* Context, const FName& ActionName, + EInputEvent InputEventType = EInputEvent::IE_Pressed); + + /** Applies the input axis with the specified name. Pass AxisValue 0.0f to reset the input axis. */ + UFUNCTION(BlueprintCallable, meta = (HidePin = "Context", DefaultToSelf = "Context")) + static void ApplyInputAxis(UObject* Context, const FName& AxisName, float AxisValue = 1.0f); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestLogCategory.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestLogCategory.h new file mode 100644 index 00000000..4fc89ec8 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestLogCategory.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +DECLARE_LOG_CATEGORY_EXTERN(LogDaeTest, Log, All); diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestParameterProviderActor.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestParameterProviderActor.h new file mode 100644 index 00000000..c4767672 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestParameterProviderActor.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include "DaeTestParameterProviderActor.generated.h" + +/** Provides a set of parameters for tests, which are run once per parameter. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API ADaeTestParameterProviderActor : public AActor +{ + GENERATED_BODY() + +public: + /** Gets the parameters to run the test with, one per run. */ + UFUNCTION(BlueprintNativeEvent) + TArray GetParameters(); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestParameterProviderActorBlueprint.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestParameterProviderActorBlueprint.h new file mode 100644 index 00000000..095fb036 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestParameterProviderActorBlueprint.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "DaeTestParameterProviderActorBlueprint.generated.h" + +/** Provides a set of parameters for tests, which are run once per parameter. */ +UCLASS(BlueprintType) +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestParameterProviderActorBlueprint : public UBlueprint +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + // UBlueprint interface + virtual bool SupportedByDefaultBlueprintFactory() const override + { + return false; + } + // End of UBlueprint interface +#endif +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestResult.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestResult.h new file mode 100644 index 00000000..80713455 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestResult.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +/** Result set of a single test. */ +class DAEDALICTESTAUTOMATIONPLUGIN_API FDaeTestResult +{ +public: + /** Name of the test. */ + FString TestName; + + /** (Optional) Why the test failed. */ + FString FailureMessage; + + /** (Optional) Why the test was skipped. */ + FString SkipReason; + + /** Time the test ran, in seconds. */ + float TimeSeconds; + + FDaeTestResult(); + FDaeTestResult(FString InTestName, float InTimeSeconds); + + /** Whether the test finished without failure, or not. */ + bool WasSuccessful() const; + + /** Whether the test has failed. */ + bool HasFailed() const; + + /** Whether this test has been skipped instead of being run. */ + bool WasSkipped() const; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteActor.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteActor.h new file mode 100644 index 00000000..cf52c2d5 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteActor.h @@ -0,0 +1,111 @@ +#pragma once + +#include "DaeTestSuiteResult.h" +#include +#include +#include "DaeTestSuiteActor.generated.h" + +class ADaeTestActor; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDaeTestSuiteActorTestSuiteSuccessfulSignature, + ADaeTestSuiteActor*, TestSuite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDaeTestSuiteActorTestSuiteFailedSignature, + ADaeTestSuiteActor*, TestSuite); + +/** Collection of automated tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API ADaeTestSuiteActor : public AActor +{ + GENERATED_BODY() + +public: + ADaeTestSuiteActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + + /** Runs all tests of this suite, in order. */ + void RunAllTests(); + + /** Whether this test suite is currently running. */ + bool IsRunning() const; + + /** Gets the test that is currently running. */ + ADaeTestActor* GetCurrentTest() const; + + /** Gets the parameter for the current test run. */ + UObject* GetCurrentTestParameter() const; + + /** Gets the name of the current test. */ + FString GetCurrentTestName() const; + + /** Results of the whole test suite. */ + FDaeTestSuiteResult GetResult() const; + + /** Event when this test suite should set up. */ + virtual void NotifyOnBeforeAll(); + + /** Event when this test suite has finished all tests. */ + virtual void NotifyOnAfterAll(); + + /** Event when this test suite should set up for the next test. */ + virtual void NotifyOnBeforeEach(); + + /** Event when this test suite should has finished a test. */ + virtual void NotifyOnAfterEach(); + + /** Event when this test suite should set up. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "BeforeAll")) + void ReceiveOnBeforeAll(); + + /** Event when this test suite has finished all tests. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "AfterAll")) + void ReceiveOnAfterAll(); + + /** Event when this test suite should set up for the next test. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "BeforeEach")) + void ReceiveOnBeforeEach(); + + /** Event when this test suite should has finished a test. This is NOT a latent event. */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "AfterEach")) + void ReceiveOnAfterEach(); + + /** Event when this test suite has successfully finished all tests. */ + FDaeTestSuiteActorTestSuiteSuccessfulSignature OnTestSuiteSuccessful; + + /** Event when any tests of this test suite have failed. */ + FDaeTestSuiteActorTestSuiteFailedSignature OnTestSuiteFailed; + +private: + /** Tests to run in this level. */ + UPROPERTY(EditInstanceOnly) + TArray Tests; + + /** Whether to automatically run this test suite on BeginPlay in Play In Editor. */ + UPROPERTY(EditInstanceOnly) + bool bRunInPIE; + + /** Index of the current test. */ + int32 TestIndex; + + /** Index of the current parameter the current test is run with. */ + int32 TestParameterIndex; + + /** Time the current test has been running, in seconds. */ + float TestTimeSeconds; + + /** Results of the whole test suite. */ + FDaeTestSuiteResult Result; + + /** Runs the next test in this test suite. */ + void RunNextTest(); + + UFUNCTION() + void OnTestSuccessful(ADaeTestActor* Test, UObject* Parameter); + + UFUNCTION() + void OnTestFailed(ADaeTestActor* Test, UObject* Parameter, const FString& FailureMessage); + + UFUNCTION() + void OnTestSkipped(ADaeTestActor* Test, UObject* Parameter, const FString& SkipReason); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteActorBlueprint.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteActorBlueprint.h new file mode 100644 index 00000000..fce0cddc --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteActorBlueprint.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "DaeTestSuiteActorBlueprint.generated.h" + +/** Suite of automated tests to be run one after another. */ +UCLASS(BlueprintType) +class DAEDALICTESTAUTOMATIONPLUGIN_API UDaeTestSuiteActorBlueprint : public UBlueprint +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + // UBlueprint interface + virtual bool SupportedByDefaultBlueprintFactory() const override + { + return false; + } + // End of UBlueprint interface +#endif +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteResult.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteResult.h new file mode 100644 index 00000000..ea0e656d --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestSuiteResult.h @@ -0,0 +1,35 @@ +#pragma once + +#include "DaeTestResult.h" + +/** Result set of a whole test suite. */ +class DAEDALICTESTAUTOMATIONPLUGIN_API FDaeTestSuiteResult +{ +public: + /** Name of the map the test suite ran in. */ + FString MapName; + + /** Name of the test suite that ran. */ + FString TestSuiteName; + + /** UTC date and time the test suite started running. */ + FDateTime Timestamp; + + /** Results of all individual tests of the test suite. */ + TArray TestResults; + + /** How many tests of the test suite have been run. */ + int32 NumTotalTests() const; + + /** How many tests of the test suite have been successful. */ + int32 NumSuccessfulTests() const; + + /** How many tests of the test suite have failed. */ + int32 NumFailedTests() const; + + /** How many tests of the test suite have been skipped instead of being run. */ + int32 NumSkippedTests() const; + + /** Combined time all tests ran, in seconds. */ + float GetTotalTimeSeconds() const; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestTriggerBox.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestTriggerBox.h new file mode 100644 index 00000000..7821dad7 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/DaeTestTriggerBox.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include "DaeTestTriggerBox.generated.h" + +/** Trigger box to be used in automated tests. */ +UCLASS() +class DAEDALICTESTAUTOMATIONPLUGIN_API ADaeTestTriggerBox : public ATriggerBox +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + + /** Whether this trigger box has been triggered at least once. */ + UFUNCTION(BlueprintPure) + bool WasTriggered() const; + +private: + /** Whether this trigger box has been triggered at least once. */ + bool bWasTriggered; + + UFUNCTION() + void OnActorBeginOverlapBroadcast(AActor* OverlappedActor, AActor* OtherActor); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/IDaedalicTestAutomationPlugin.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/IDaedalicTestAutomationPlugin.h new file mode 100644 index 00000000..75039e4d --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPlugin/Public/IDaedalicTestAutomationPlugin.h @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IDaedalicTestAutomationPlugin : public IModuleInterface +{ + +public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IDaedalicTestAutomationPlugin& Get() + { + return FModuleManager::LoadModuleChecked< IDaedalicTestAutomationPlugin >( "DaedalicTestAutomationPlugin" ); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "DaedalicTestAutomationPlugin" ); + } +}; + diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/DaedalicTestAutomationPluginEditor.Build.cs b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/DaedalicTestAutomationPluginEditor.Build.cs new file mode 100644 index 00000000..e93ccd0e --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/DaedalicTestAutomationPluginEditor.Build.cs @@ -0,0 +1,51 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class DaedalicTestAutomationPluginEditor : ModuleRules + { + public DaedalicTestAutomationPluginEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "DaedalicTestAutomationPlugin", + "UnrealEd", + "BlueprintGraph", + "AssetTools" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + // ... add private dependencies that you statically link with here ... + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestActorBlueprint.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestActorBlueprint.cpp new file mode 100644 index 00000000..83442365 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestActorBlueprint.cpp @@ -0,0 +1,35 @@ +#include "AssetTypeActions_DaeTestActorBlueprint.h" +#include "DaeTestActorBlueprint.h" +#include "DaeTestActorBlueprintFactory.h" + +#define LOCTEXT_NAMESPACE "DaedalicTestAutomationPlugin" + +FAssetTypeActions_DaeTestActorBlueprint::FAssetTypeActions_DaeTestActorBlueprint( + EAssetTypeCategories::Type InAssetTypeCategory) + : AssetTypeCategory(InAssetTypeCategory) +{ +} + +FText FAssetTypeActions_DaeTestActorBlueprint::GetName() const +{ + return NSLOCTEXT("DaedalicTestAutomationPlugin", "AssetTypeActions_DaeTestActorBlueprint", + "Test Actor Blueprint"); +} + +UClass* FAssetTypeActions_DaeTestActorBlueprint::GetSupportedClass() const +{ + return UDaeTestActorBlueprint::StaticClass(); +} + +uint32 FAssetTypeActions_DaeTestActorBlueprint::GetCategories() +{ + return AssetTypeCategory; +} + +UFactory* FAssetTypeActions_DaeTestActorBlueprint::GetFactoryForBlueprintType( + UBlueprint* InBlueprint) const +{ + return NewObject(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestParameterProviderActorBlueprint.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestParameterProviderActorBlueprint.cpp new file mode 100644 index 00000000..27ae1fc2 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestParameterProviderActorBlueprint.cpp @@ -0,0 +1,37 @@ +#include "AssetTypeActions_DaeTestParameterProviderActorBlueprint.h" +#include "DaeTestParameterProviderActorBlueprint.h" +#include "DaeTestParameterProviderActorBlueprintFactory.h" + +#define LOCTEXT_NAMESPACE "DaedalicTestAutomationPlugin" + +FAssetTypeActions_DaeTestParameterProviderActorBlueprint:: + FAssetTypeActions_DaeTestParameterProviderActorBlueprint( + EAssetTypeCategories::Type InAssetTypeCategory) + : AssetTypeCategory(InAssetTypeCategory) +{ +} + +FText FAssetTypeActions_DaeTestParameterProviderActorBlueprint::GetName() const +{ + return NSLOCTEXT("DaedalicTestAutomationPlugin", + "AssetTypeActions_DaeTestParameterProviderActorBlueprint", + "Test Parameter Provider Actor Blueprint"); +} + +UClass* FAssetTypeActions_DaeTestParameterProviderActorBlueprint::GetSupportedClass() const +{ + return UDaeTestParameterProviderActorBlueprint::StaticClass(); +} + +uint32 FAssetTypeActions_DaeTestParameterProviderActorBlueprint::GetCategories() +{ + return AssetTypeCategory; +} + +UFactory* FAssetTypeActions_DaeTestParameterProviderActorBlueprint::GetFactoryForBlueprintType( + UBlueprint* InBlueprint) const +{ + return NewObject(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestSuiteActorBlueprint.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestSuiteActorBlueprint.cpp new file mode 100644 index 00000000..c5db1db9 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AssetTypeActions_DaeTestSuiteActorBlueprint.cpp @@ -0,0 +1,35 @@ +#include "AssetTypeActions_DaeTestSuiteActorBlueprint.h" +#include "DaeTestSuiteActorBlueprint.h" +#include "DaeTestSuiteActorBlueprintFactory.h" + +#define LOCTEXT_NAMESPACE "DaedalicTestAutomationPlugin" + +FAssetTypeActions_DaeTestSuiteActorBlueprint::FAssetTypeActions_DaeTestSuiteActorBlueprint( + EAssetTypeCategories::Type InAssetTypeCategory) + : AssetTypeCategory(InAssetTypeCategory) +{ +} + +FText FAssetTypeActions_DaeTestSuiteActorBlueprint::GetName() const +{ + return NSLOCTEXT("DaedalicTestAutomationPlugin", "AssetTypeActions_DaeTestSuiteActorBlueprint", + "Test Suite Actor Blueprint"); +} + +UClass* FAssetTypeActions_DaeTestSuiteActorBlueprint::GetSupportedClass() const +{ + return UDaeTestSuiteActorBlueprint::StaticClass(); +} + +uint32 FAssetTypeActions_DaeTestSuiteActorBlueprint::GetCategories() +{ + return AssetTypeCategory; +} + +UFactory* FAssetTypeActions_DaeTestSuiteActorBlueprint::GetFactoryForBlueprintType( + UBlueprint* InBlueprint) const +{ + return NewObject(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.cpp new file mode 100644 index 00000000..8af06ca2 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.cpp @@ -0,0 +1,33 @@ +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.h" +#include "DaeTestSuiteActor.h" +#include +#include +#include + +bool FDaeTestAutomationPluginWaitForEndOfTestSuite::Update() +{ + if (!GEditor) + { + return false; + } + + if (!GEditor->PlayWorld) + { + return false; + } + + if (!Context.CurrentTestSuite) + { + for (TActorIterator Iter(GEditor->PlayWorld); Iter; ++Iter) + { + Context.CurrentTestSuite = *Iter; + } + } + + if (Context.CurrentTestSuite) + { + return !Context.CurrentTestSuite->IsRunning(); + } + + return false; +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.cpp new file mode 100644 index 00000000..2f644caa --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.cpp @@ -0,0 +1,34 @@ +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.h" +#include "DaeTestEditorLogCategory.h" +#include +#include +#include + +void FDaeTestAutomationPluginAutomationTestFrameworkIntegration::SetTestMapPath( + const FString& InTestMapPath) +{ + UE_LOG(LogDaeTestEditor, Log, TEXT("Discovering tests from: %s"), *InTestMapPath); + + // Unregister existing tests. + Tests.Empty(); + + // Register new tests (based on FLoadAllMapsInEditorTest). + TArray PackageFiles; + FEditorFileUtils::FindAllPackageFiles(PackageFiles); + + // Iterate over all files, adding the ones with the map extension. + const FString PatternToCheck = FString::Printf(TEXT("/%s/"), *InTestMapPath); + + for (const FString& Filename : PackageFiles) + { + if (FPaths::GetExtension(Filename, true) == FPackageName::GetMapPackageExtension() + && Filename.Contains(*PatternToCheck)) + { + TSharedPtr NewTest = + MakeShareable(new FDaeTestAutomationPluginAutomationTestFrameworkTest(Filename)); + Tests.Add(NewTest); + + UE_LOG(LogDaeTestEditor, Log, TEXT("Discovered test: %s"), *NewTest->GetMapName()); + } + } +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.cpp new file mode 100644 index 00000000..859cff77 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.cpp @@ -0,0 +1,66 @@ +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.h" +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.h" +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.h" +#include "DaeTestEditorLogCategory.h" +#include +#include +#include + +FDaeTestAutomationPluginAutomationTestFrameworkTest:: + FDaeTestAutomationPluginAutomationTestFrameworkTest(const FString& InMapName) + : FAutomationTestBase(InMapName, false) + , MapName(InMapName) +{ + // Test is automatically registered in FAutomationTestBase base class constructor. +} + +uint32 FDaeTestAutomationPluginAutomationTestFrameworkTest::GetTestFlags() const +{ + return EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter; +} + +uint32 FDaeTestAutomationPluginAutomationTestFrameworkTest::GetRequiredDeviceNum() const +{ + return 1; +} + +FString FDaeTestAutomationPluginAutomationTestFrameworkTest::GetTestSourceFileName() const +{ + return GetMapName(); +} + +int32 FDaeTestAutomationPluginAutomationTestFrameworkTest::GetTestSourceFileLine() const +{ + return 0; +} + +FString FDaeTestAutomationPluginAutomationTestFrameworkTest::GetMapName() const +{ + return MapName; +} + +void FDaeTestAutomationPluginAutomationTestFrameworkTest::GetTests( + TArray& OutBeautifiedNames, TArray& OutTestCommands) const +{ + OutBeautifiedNames.Add(GetBeautifiedTestName()); + OutTestCommands.Add(FString()); +} + +bool FDaeTestAutomationPluginAutomationTestFrameworkTest::RunTest(const FString& Parameters) +{ + UE_LOG(LogDaeTestEditor, Log, TEXT("Running test for map: %s"), *MapName); + + FDaeTestAutomationPluginAutomationTestFrameworkTestContext Context; + + ADD_LATENT_AUTOMATION_COMMAND(FEditorLoadMap(MapName)); + ADD_LATENT_AUTOMATION_COMMAND(FStartPIECommand(false)); + ADD_LATENT_AUTOMATION_COMMAND(FDaeTestAutomationPluginWaitForEndOfTestSuite(Context)); + ADD_LATENT_AUTOMATION_COMMAND(FEndPlayMapCommand()); + + return true; +} + +FString FDaeTestAutomationPluginAutomationTestFrameworkTest::GetBeautifiedTestName() const +{ + return TEXT("DaedalicTestAutomationPlugin.") + FPaths::GetBaseFilename(MapName); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.cpp new file mode 100644 index 00000000..d123fe43 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.cpp @@ -0,0 +1,7 @@ +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.h" + +FDaeTestAutomationPluginAutomationTestFrameworkTestContext:: + FDaeTestAutomationPluginAutomationTestFrameworkTestContext() + : CurrentTestSuite(nullptr) +{ +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestActorBlueprintFactory.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestActorBlueprintFactory.cpp new file mode 100644 index 00000000..b9712118 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestActorBlueprintFactory.cpp @@ -0,0 +1,70 @@ +#include "DaeTestActorBlueprintFactory.h" +#include "DaeTestActor.h" +#include "DaeTestActorBlueprint.h" +#include +#include +#include +#include +#include +#include +#include + +UDaeTestActorBlueprintFactory::UDaeTestActorBlueprintFactory( + const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = UDaeTestActorBlueprint::StaticClass(); +} + +UObject* UDaeTestActorBlueprintFactory::FactoryCreateNew(UClass* Class, UObject* InParent, + FName Name, EObjectFlags Flags, + UObject* Context, FFeedbackContext* Warn) +{ + // Create blueprint asset. + UBlueprint* Blueprint = + FKismetEditorUtilities::CreateBlueprint(ADaeTestActor::StaticClass(), InParent, Name, + BPTYPE_Normal, + UDaeTestActorBlueprint::StaticClass(), + UBlueprintGeneratedClass::StaticClass()); + + if (!IsValid(Blueprint)) + { + return nullptr; + } + + // Create special blueprint graph. + UEdGraph* EdGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, + TEXT("Test Blueprint Graph"), + UEdGraph::StaticClass(), + UEdGraphSchema_K2::StaticClass()); + if (Blueprint->UbergraphPages.Num()) + { + FBlueprintEditorUtils::RemoveGraphs(Blueprint, Blueprint->UbergraphPages); + } + + FBlueprintEditorUtils::AddUbergraphPage(Blueprint, EdGraph); + Blueprint->LastEditedDocuments.Add(EdGraph); + EdGraph->bAllowDeletion = false; + + UBlueprintEditorSettings* Settings = GetMutableDefault(); + + if (Settings && Settings->bSpawnDefaultBlueprintNodes) + { + int32 NodePositionY = 0; + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnAssume")), + ADaeTestActor::StaticClass(), NodePositionY); + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnArrange")), + ADaeTestActor::StaticClass(), NodePositionY); + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, FName(TEXT("ReceiveOnAct")), + ADaeTestActor::StaticClass(), NodePositionY); + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnAssert")), + ADaeTestActor::StaticClass(), NodePositionY); + } + + return Blueprint; +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestEditorLogCategory.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestEditorLogCategory.cpp new file mode 100644 index 00000000..e324f4b8 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestEditorLogCategory.cpp @@ -0,0 +1,3 @@ +#include "DaeTestEditorLogCategory.h" + +DEFINE_LOG_CATEGORY(LogDaeTestEditor); diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestParameterProviderActorBlueprintFactory.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestParameterProviderActorBlueprintFactory.cpp new file mode 100644 index 00000000..5eb59b7d --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestParameterProviderActorBlueprintFactory.cpp @@ -0,0 +1,30 @@ +#include "DaeTestParameterProviderActorBlueprintFactory.h" +#include "DaeTestParameterProviderActor.h" +#include "DaeTestParameterProviderActorBlueprint.h" +#include +#include +#include +#include +#include +#include +#include + +UDaeTestParameterProviderActorBlueprintFactory::UDaeTestParameterProviderActorBlueprintFactory( + const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = UDaeTestParameterProviderActorBlueprint::StaticClass(); +} + +UObject* UDaeTestParameterProviderActorBlueprintFactory::FactoryCreateNew( + UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, + FFeedbackContext* Warn) +{ + // Create blueprint asset. + return FKismetEditorUtilities::CreateBlueprint( + ADaeTestParameterProviderActor::StaticClass(), InParent, Name, BPTYPE_Normal, + UDaeTestParameterProviderActorBlueprint::StaticClass(), + UBlueprintGeneratedClass::StaticClass()); +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestSuiteActorBlueprintFactory.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestSuiteActorBlueprintFactory.cpp new file mode 100644 index 00000000..add0125d --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaeTestSuiteActorBlueprintFactory.cpp @@ -0,0 +1,76 @@ +#include "DaeTestSuiteActorBlueprintFactory.h" +#include "DaeTestSuiteActor.h" +#include "DaeTestSuiteActorBlueprint.h" +#include +#include +#include +#include +#include +#include +#include + +UDaeTestSuiteActorBlueprintFactory::UDaeTestSuiteActorBlueprintFactory( + const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = UDaeTestSuiteActorBlueprint::StaticClass(); +} + +UObject* UDaeTestSuiteActorBlueprintFactory::FactoryCreateNew(UClass* Class, UObject* InParent, + FName Name, EObjectFlags Flags, + UObject* Context, + FFeedbackContext* Warn) +{ + // Create blueprint asset. + UBlueprint* Blueprint = + FKismetEditorUtilities::CreateBlueprint(ADaeTestSuiteActor::StaticClass(), InParent, Name, + BPTYPE_Normal, + UDaeTestSuiteActorBlueprint::StaticClass(), + UBlueprintGeneratedClass::StaticClass()); + + if (!IsValid(Blueprint)) + { + return nullptr; + } + + // Create special blueprint graph. + UEdGraph* EdGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, + TEXT("Test Suite Blueprint Graph"), + UEdGraph::StaticClass(), + UEdGraphSchema_K2::StaticClass()); + if (Blueprint->UbergraphPages.Num()) + { + FBlueprintEditorUtils::RemoveGraphs(Blueprint, Blueprint->UbergraphPages); + } + + FBlueprintEditorUtils::AddUbergraphPage(Blueprint, EdGraph); + Blueprint->LastEditedDocuments.Add(EdGraph); + EdGraph->bAllowDeletion = false; + + UBlueprintEditorSettings* Settings = GetMutableDefault(); + + if (Settings && Settings->bSpawnDefaultBlueprintNodes) + { + int32 NodePositionY = 0; + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnBeforeAll")), + ADaeTestSuiteActor::StaticClass(), + NodePositionY); + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnAfterAll")), + ADaeTestSuiteActor::StaticClass(), + NodePositionY); + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnBeforeEach")), + ADaeTestSuiteActor::StaticClass(), + NodePositionY); + FKismetEditorUtilities::AddDefaultEventNode(Blueprint, EdGraph, + FName(TEXT("ReceiveOnAfterEach")), + ADaeTestSuiteActor::StaticClass(), + NodePositionY); + } + + return Blueprint; +} diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaedalicTestAutomationPluginEditor.cpp b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaedalicTestAutomationPluginEditor.cpp new file mode 100644 index 00000000..3f7b4253 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Private/DaedalicTestAutomationPluginEditor.cpp @@ -0,0 +1,128 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.h" +#include "AssetTypeActions_DaeTestActorBlueprint.h" +#include "AssetTypeActions_DaeTestParameterProviderActorBlueprint.h" +#include "AssetTypeActions_DaeTestSuiteActorBlueprint.h" +#include "DaeTestAutomationPluginSettings.h" +#include "DaedalicTestAutomationPluginEditorClasses.h" +#include "IDaedalicTestAutomationPluginEditor.h" +#include +#include +#include +#include +#include +#include + +#define LOCTEXT_NAMESPACE "DaedalicTestAutomationPlugin" + +class FDaedalicTestAutomationPluginEditor : public IDaedalicTestAutomationPluginEditor +{ +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + /** Asset catetory for test automation assets. */ + EAssetTypeCategories::Type DaedalicTestAutomationAssetCategory; + + /** Asset type actions registered by this module. */ + TArray> AssetTypeActions; + + /** Integration with the Unreal Automation Test Framework. */ + FDaeTestAutomationPluginAutomationTestFrameworkIntegration AutomationTestFrameworkIntegration; + + void RegisterAssetTypeAction(class IAssetTools& AssetTools, + TSharedRef Action); + + void OnTestMapPathChanged(const FString& NewTestMapPath); +}; + +IMPLEMENT_MODULE(FDaedalicTestAutomationPluginEditor, DaedalicTestAutomationPluginEditor) + +void FDaedalicTestAutomationPluginEditor::StartupModule() +{ + // Register asset types. + IAssetTools& AssetTools = + FModuleManager::LoadModuleChecked("AssetTools").Get(); + + DaedalicTestAutomationAssetCategory = + AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("DaedalicTestAutomationPlugin")), + NSLOCTEXT("DaedalicTestAutomationPlugin", + "DaedalicTestAutomationAssetCategory", + "Test Automation")); + + TSharedRef TestActorBlueprintAction = MakeShareable( + new FAssetTypeActions_DaeTestActorBlueprint(DaedalicTestAutomationAssetCategory)); + RegisterAssetTypeAction(AssetTools, TestActorBlueprintAction); + + TSharedRef TestSuiteActorBlueprintAction = MakeShareable( + new FAssetTypeActions_DaeTestSuiteActorBlueprint(DaedalicTestAutomationAssetCategory)); + RegisterAssetTypeAction(AssetTools, TestSuiteActorBlueprintAction); + + TSharedRef TestParameterProviderActorBlueprintAction = + MakeShareable(new FAssetTypeActions_DaeTestParameterProviderActorBlueprint( + DaedalicTestAutomationAssetCategory)); + RegisterAssetTypeAction(AssetTools, TestParameterProviderActorBlueprintAction); + + // Register settings. + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + UDaeTestAutomationPluginSettings* TestAutomationPluginSettings = + GetMutableDefault(); + + ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings( + "Project", "Plugins", "DaedalicTestAutomationPlugin", + NSLOCTEXT("DaedalicTestAutomationPlugin", "DaeTestAutomationPluginSettings.DisplayName", + "Daedalic Test Automation Plugin"), + NSLOCTEXT("DaedalicTestAutomationPlugin", "DaeTestAutomationPluginSettings.Description", + "Configure the discovery of automated tests."), + TestAutomationPluginSettings); + + TestAutomationPluginSettings->OnTestMapPathChanged.AddRaw( + this, &FDaedalicTestAutomationPluginEditor::OnTestMapPathChanged); + + OnTestMapPathChanged(TestAutomationPluginSettings->TestMapPath); + } +} + +void FDaedalicTestAutomationPluginEditor::ShutdownModule() +{ + // Unregister asset types. + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools& AssetToolsModule = + FModuleManager::GetModuleChecked("AssetTools").Get(); + for (auto& AssetTypeAction : AssetTypeActions) + { + if (AssetTypeAction.IsValid()) + { + AssetToolsModule.UnregisterAssetTypeActions(AssetTypeAction.ToSharedRef()); + } + } + } + + AssetTypeActions.Empty(); + + // Unregister settings. + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Editor", "Plugins", "DaedalicTestAutomationPlugin"); + } +} + +void FDaedalicTestAutomationPluginEditor::RegisterAssetTypeAction( + class IAssetTools& AssetTools, TSharedRef Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + AssetTypeActions.Add(Action); +} + +void FDaedalicTestAutomationPluginEditor::OnTestMapPathChanged(const FString& NewTestMapPath) +{ + // Discover tests. + AutomationTestFrameworkIntegration.SetTestMapPath(NewTestMapPath); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestActorBlueprint.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestActorBlueprint.h new file mode 100644 index 00000000..f7fd3bbb --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestActorBlueprint.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +class UFactory; + +/** Asset action for creating new test actor blueprints with special initial blueprint graphs. */ +class FAssetTypeActions_DaeTestActorBlueprint : public FAssetTypeActions_Blueprint +{ +public: + FAssetTypeActions_DaeTestActorBlueprint(EAssetTypeCategories::Type InAssetTypeCategory); + + // IAssetTypeActions Implementation + virtual FText GetName() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + // End IAssetTypeActions Implementation + + // FAssetTypeActions_Blueprint interface + virtual UFactory* GetFactoryForBlueprintType(UBlueprint* InBlueprint) const override; + +private: + EAssetTypeCategories::Type AssetTypeCategory; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestParameterProviderActorBlueprint.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestParameterProviderActorBlueprint.h new file mode 100644 index 00000000..9675afe3 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestParameterProviderActorBlueprint.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +class UFactory; + +/** Asset action for creating new test parameter provider actor blueprints with special initial blueprint graphs. */ +class FAssetTypeActions_DaeTestParameterProviderActorBlueprint : public FAssetTypeActions_Blueprint +{ +public: + FAssetTypeActions_DaeTestParameterProviderActorBlueprint( + EAssetTypeCategories::Type InAssetTypeCategory); + + // IAssetTypeActions Implementation + virtual FText GetName() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + // End IAssetTypeActions Implementation + + // FAssetTypeActions_Blueprint interface + virtual UFactory* GetFactoryForBlueprintType(UBlueprint* InBlueprint) const override; + +private: + EAssetTypeCategories::Type AssetTypeCategory; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestSuiteActorBlueprint.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestSuiteActorBlueprint.h new file mode 100644 index 00000000..6d7769ad --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AssetTypeActions_DaeTestSuiteActorBlueprint.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +class UFactory; + +/** Asset action for creating new test suite actor blueprints with special initial blueprint graphs. */ +class FAssetTypeActions_DaeTestSuiteActorBlueprint : public FAssetTypeActions_Blueprint +{ +public: + FAssetTypeActions_DaeTestSuiteActorBlueprint(EAssetTypeCategories::Type InAssetTypeCategory); + + // IAssetTypeActions Implementation + virtual FText GetName() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + // End IAssetTypeActions Implementation + + // FAssetTypeActions_Blueprint interface + virtual UFactory* GetFactoryForBlueprintType(UBlueprint* InBlueprint) const override; + +private: + EAssetTypeCategories::Type AssetTypeCategory; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.h new file mode 100644 index 00000000..752c2b05 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkCommands.h @@ -0,0 +1,12 @@ +#pragma once + +#include "DaeTestAutomationPluginAutomationTestFrameworkTestContext.h" +#include +#include + +class ADaeTestSuiteActor; + +/** Waits for the current test suite to finish. */ +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER( + FDaeTestAutomationPluginWaitForEndOfTestSuite, + FDaeTestAutomationPluginAutomationTestFrameworkTestContext, Context); diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.h new file mode 100644 index 00000000..8b0dac6c --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkIntegration.h @@ -0,0 +1,19 @@ +#pragma once + +#include "DaeTestAutomationPluginAutomationTestFrameworkTest.h" +#include + +/** Integration with the Unreal Automation Test Framework. Discovers tests based on the plugin settings. */ +class FDaeTestAutomationPluginAutomationTestFrameworkIntegration +{ +public: + /** Sets the path to look for test maps in, re-discovering tests afterwards. */ + void SetTestMapPath(const FString& InTestMapPath); + +private: + /** Path to look for test maps in. */ + FString TestMapPath; + + /** Currently registered automation tests. */ + TArray> Tests; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.h new file mode 100644 index 00000000..80e61767 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTest.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +/** Single test to be registered with the Unreal Automation Test Framework. Implementation based on IMPLEMENT_SIMPLE_AUTOMATION_TEST_PRIVATE macro. */ +class FDaeTestAutomationPluginAutomationTestFrameworkTest : FAutomationTestBase +{ +public: + FDaeTestAutomationPluginAutomationTestFrameworkTest(const FString& InMapName); + + virtual uint32 GetTestFlags() const override; + virtual uint32 GetRequiredDeviceNum() const override; + virtual FString GetTestSourceFileName() const override; + virtual int32 GetTestSourceFileLine() const override; + + FString GetMapName() const; + +protected: + virtual void GetTests(TArray& OutBeautifiedNames, + TArray& OutTestCommands) const override; + virtual bool RunTest(const FString& Parameters) override; + virtual FString GetBeautifiedTestName() const override; + +private: + FString MapName; +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.h new file mode 100644 index 00000000..e61580a8 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/AutomationTestFramework/DaeTestAutomationPluginAutomationTestFrameworkTestContext.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class ADaeTestSuiteActor; + +/** Context to run a single test for the Unreal Automation Test Framework within. */ +class FDaeTestAutomationPluginAutomationTestFrameworkTestContext +{ +public: + ADaeTestSuiteActor* CurrentTestSuite; + + FDaeTestAutomationPluginAutomationTestFrameworkTestContext(); +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestActorBlueprintFactory.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestActorBlueprintFactory.h new file mode 100644 index 00000000..2e8366b5 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestActorBlueprintFactory.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "DaeTestActorBlueprintFactory.generated.h" + +/** Factory for creating new test actor blueprints with special initial blueprint graphs. */ +UCLASS(HideCategories = Object, MinimalAPI) +class UDaeTestActorBlueprintFactory : public UFactory +{ + GENERATED_BODY() + +public: + UDaeTestActorBlueprintFactory( + const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, + EObjectFlags Flags, UObject* Context, + FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestEditorLogCategory.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestEditorLogCategory.h new file mode 100644 index 00000000..14e8400f --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestEditorLogCategory.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +DECLARE_LOG_CATEGORY_EXTERN(LogDaeTestEditor, Log, All); diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestParameterProviderActorBlueprintFactory.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestParameterProviderActorBlueprintFactory.h new file mode 100644 index 00000000..93c9fd49 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestParameterProviderActorBlueprintFactory.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "DaeTestParameterProviderActorBlueprintFactory.generated.h" + +/** Factory for creating new test parameter provider actor blueprints with special initial blueprint graphs. */ +UCLASS(HideCategories = Object, MinimalAPI) +class UDaeTestParameterProviderActorBlueprintFactory : public UFactory +{ + GENERATED_BODY() + +public: + UDaeTestParameterProviderActorBlueprintFactory( + const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, + EObjectFlags Flags, UObject* Context, + FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestSuiteActorBlueprintFactory.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestSuiteActorBlueprintFactory.h new file mode 100644 index 00000000..3e212ebf --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/DaeTestSuiteActorBlueprintFactory.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "DaeTestSuiteActorBlueprintFactory.generated.h" + +/** Factory for creating new test suite actor blueprints with special initial blueprint graphs. */ +UCLASS(HideCategories = Object, MinimalAPI) +class UDaeTestSuiteActorBlueprintFactory : public UFactory +{ + GENERATED_BODY() + +public: + UDaeTestSuiteActorBlueprintFactory( + const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, + EObjectFlags Flags, UObject* Context, + FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/IDaedalicTestAutomationPluginEditor.h b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/IDaedalicTestAutomationPluginEditor.h new file mode 100644 index 00000000..f99efa97 --- /dev/null +++ b/Source/RTSProject/Plugins/DaedalicTestAutomationPlugin/Source/DaedalicTestAutomationPluginEditor/Public/IDaedalicTestAutomationPluginEditor.h @@ -0,0 +1,37 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "CoreMinimal.h" + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IDaedalicTestAutomationPluginEditor : public IModuleInterface +{ +public: + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IDaedalicTestAutomationPluginEditor& Get() + { + return FModuleManager::LoadModuleChecked( + "DaedalicTestAutomationPluginEditor"); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("DaedalicTestAutomationPluginEditor"); + } +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Config/DefaultRealTimeStrategy.ini b/Source/RTSProject/Plugins/RealTimeStrategy/Config/DefaultRealTimeStrategy.ini new file mode 100644 index 00000000..27726326 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Config/DefaultRealTimeStrategy.ini @@ -0,0 +1,3 @@ +[CoreRedirects] ++ClassRedirects=(OldName="RTSCharacter",NewName="/Script/Engine.Character") ++ClassRedirects=(OldName="RTSPawn",NewName="/Script/Engine.Pawn") diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Config/FilterPlugin.ini b/Source/RTSProject/Plugins/RealTimeStrategy/Config/FilterPlugin.ini new file mode 100644 index 00000000..ccebca2f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/BB_RTSPawnBlackboard.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/BB_RTSPawnBlackboard.uasset new file mode 100644 index 00000000..b4f96536 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/BB_RTSPawnBlackboard.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/BT_RTSPawnBehaviorTree.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/BT_RTSPawnBehaviorTree.uasset new file mode 100644 index 00000000..b9c90492 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/BT_RTSPawnBehaviorTree.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BB_RTSPlayerBlackboard.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BB_RTSPlayerBlackboard.uasset new file mode 100644 index 00000000..9c440cab Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BB_RTSPlayerBlackboard.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BTTask_RTSApplyBuildOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BTTask_RTSApplyBuildOrder.uasset new file mode 100644 index 00000000..e620e3a6 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BTTask_RTSApplyBuildOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BT_RTSPlayerBehaviorTree.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BT_RTSPlayerBehaviorTree.uasset new file mode 100644 index 00000000..b4087979 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/PlayerAI/BT_RTSPlayerBehaviorTree.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CanGatherFrom.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CanGatherFrom.uasset new file mode 100644 index 00000000..d6b84db9 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CanGatherFrom.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CanMove.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CanMove.uasset new file mode 100644 index 00000000..b77ab589 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CanMove.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_ConstructionIsFinished.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_ConstructionIsFinished.uasset new file mode 100644 index 00000000..93646e9f Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_ConstructionIsFinished.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CooldownIsReady.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CooldownIsReady.uasset new file mode 100644 index 00000000..de804c90 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_CooldownIsReady.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasAttackOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasAttackOrder.uasset new file mode 100644 index 00000000..d2e75f21 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasAttackOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasBeginConstructionOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasBeginConstructionOrder.uasset new file mode 100644 index 00000000..e4933698 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasBeginConstructionOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasContinueConstructionOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasContinueConstructionOrder.uasset new file mode 100644 index 00000000..4cff0f5d Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasContinueConstructionOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasGatherOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasGatherOrder.uasset new file mode 100644 index 00000000..ee7fc415 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasGatherOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasLeftChaseRadius.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasLeftChaseRadius.uasset new file mode 100644 index 00000000..437ecf57 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasLeftChaseRadius.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasMoveOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasMoveOrder.uasset new file mode 100644 index 00000000..07c7aec3 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasMoveOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasReturnResourcesOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasReturnResourcesOrder.uasset new file mode 100644 index 00000000..86da90b5 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_HasReturnResourcesOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsAtConstructionSite.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsAtConstructionSite.uasset new file mode 100644 index 00000000..b76dab06 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsAtConstructionSite.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsCarryingResources.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsCarryingResources.uasset new file mode 100644 index 00000000..dab75329 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsCarryingResources.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsGatheringResources.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsGatheringResources.uasset new file mode 100644 index 00000000..0fa541dc Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsGatheringResources.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsIdle.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsIdle.uasset new file mode 100644 index 00000000..c885cd93 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsIdle.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsInRange.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsInRange.uasset new file mode 100644 index 00000000..9504f925 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsInRange.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsInRangeToLocation.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsInRangeToLocation.uasset new file mode 100644 index 00000000..597c7eb5 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_IsInRangeToLocation.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_ResourceSourceIsReady.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_ResourceSourceIsReady.uasset new file mode 100644 index 00000000..a4a292cb Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_ResourceSourceIsReady.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_TargetIsAlive.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_TargetIsAlive.uasset new file mode 100644 index 00000000..f50ae768 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTDecorator_TargetIsAlive.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTService_AcquireTargets.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTService_AcquireTargets.uasset new file mode 100644 index 00000000..3f463f15 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTService_AcquireTargets.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_AssignToConstructionSite.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_AssignToConstructionSite.uasset new file mode 100644 index 00000000..81f49856 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_AssignToConstructionSite.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_BeginConstruction.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_BeginConstruction.uasset new file mode 100644 index 00000000..2c25878d Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_BeginConstruction.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_ContinueGatherResources.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_ContinueGatherResources.uasset new file mode 100644 index 00000000..beee48db Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_ContinueGatherResources.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_FaceTarget.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_FaceTarget.uasset new file mode 100644 index 00000000..9ce4ab0d Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_FaceTarget.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_GatherResources.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_GatherResources.uasset new file mode 100644 index 00000000..ce3de385 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_GatherResources.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueMoveOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueMoveOrder.uasset new file mode 100644 index 00000000..ccc43fdf Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueMoveOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueReturnResourcesOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueReturnResourcesOrder.uasset new file mode 100644 index 00000000..df3003c6 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueReturnResourcesOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueStopOrder.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueStopOrder.uasset new file mode 100644 index 00000000..8b407c96 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_IssueStopOrder.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_LeaveConstructionSite.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_LeaveConstructionSite.uasset new file mode 100644 index 00000000..28089b38 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_LeaveConstructionSite.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_MoveIntoBlackboardRangeToActor.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_MoveIntoBlackboardRangeToActor.uasset new file mode 100644 index 00000000..c02327b5 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_MoveIntoBlackboardRangeToActor.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_MoveIntoBlackboardRangeToLocation.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_MoveIntoBlackboardRangeToLocation.uasset new file mode 100644 index 00000000..4168af69 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_MoveIntoBlackboardRangeToLocation.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_ReturnResources.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_ReturnResources.uasset new file mode 100644 index 00000000..2b5e3c9f Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_ReturnResources.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetAttackRange.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetAttackRange.uasset new file mode 100644 index 00000000..4e0cb7f0 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetAttackRange.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetConstructionRange.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetConstructionRange.uasset new file mode 100644 index 00000000..4fb18074 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetConstructionRange.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetGatherRange.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetGatherRange.uasset new file mode 100644 index 00000000..c04c4de1 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_SetGatherRange.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_UseAttack.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_UseAttack.uasset new file mode 100644 index 00000000..f073de70 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/AI/RTSBTTask_UseAttack.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/BP_RTSBuildingCursor.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/BP_RTSBuildingCursor.uasset new file mode 100644 index 00000000..bbdde727 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/BP_RTSBuildingCursor.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/M_RTSBuildingCursorInvalid.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/M_RTSBuildingCursorInvalid.uasset new file mode 100644 index 00000000..97a9c59b Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/M_RTSBuildingCursorInvalid.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/M_RTSBuildingCursorValid.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/M_RTSBuildingCursorValid.uasset new file mode 100644 index 00000000..d8bae2e6 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Construction/M_RTSBuildingCursorValid.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Data/DT_RTSGameplayTags.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Data/DT_RTSGameplayTags.uasset new file mode 100644 index 00000000..355d7092 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Data/DT_RTSGameplayTags.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/FogOfWar/M_RTSFogOfWar.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/FogOfWar/M_RTSFogOfWar.uasset new file mode 100644 index 00000000..aaa33507 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/FogOfWar/M_RTSFogOfWar.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Minimap/M_RTSFogOfWarMinimap.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Minimap/M_RTSFogOfWarMinimap.uasset new file mode 100644 index 00000000..948352d6 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Minimap/M_RTSFogOfWarMinimap.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Minimap/WBP_RTSMinimapWidget.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Minimap/WBP_RTSMinimapWidget.uasset new file mode 100644 index 00000000..59788000 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Minimap/WBP_RTSMinimapWidget.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/M_RTSSelectionCircle.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/M_RTSSelectionCircle.uasset new file mode 100644 index 00000000..43d9a07b Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/M_RTSSelectionCircle.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/T_RTSSelectionCircle.png b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/T_RTSSelectionCircle.png new file mode 100644 index 00000000..d05bab36 Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/T_RTSSelectionCircle.png differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/T_RTSSelectionCircle.uasset b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/T_RTSSelectionCircle.uasset new file mode 100644 index 00000000..24fb4c7b Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Content/Selection/T_RTSSelectionCircle.uasset differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/RealTimeStrategy.uplugin b/Source/RTSProject/Plugins/RealTimeStrategy/RealTimeStrategy.uplugin new file mode 100644 index 00000000..8c29d074 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/RealTimeStrategy.uplugin @@ -0,0 +1,26 @@ +{ + "FileVersion" : 1, + "Version" : 1, + "VersionName" : "1.0.0", + "FriendlyName" : "Real-Time Strategy", + "Description" : "Provides a gameplay framework for developing real-time strategy games.", + "Category" : "RTS", + "CreatedBy" : "Nick Pruehs", + "CreatedByURL" : "https://github.com/npruehs", + "DocsURL" : "https://github.com/npruehs/ue4-rts", + "MarketplaceURL" : "", + "SupportURL" : "https://github.com/npruehs/ue4-rts/issues", + "EnabledByDefault" : true, + "CanContainContent" : true, + "IsBetaVersion" : false, + "Installed" : false, + "Modules" : + [ + { + "Name" : "RealTimeStrategy", + "Type" : "Runtime", + "LoadingPhase" : "Default", + "WhitelistPlatforms": [ "Win64" ] + } + ] +} \ No newline at end of file diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Resources/Icon128.png b/Source/RTSProject/Plugins/RealTimeStrategy/Resources/Icon128.png new file mode 100644 index 00000000..7caa6cea Binary files /dev/null and b/Source/RTSProject/Plugins/RealTimeStrategy/Resources/Icon128.png differ diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSAttackComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSAttackComponent.h new file mode 100644 index 00000000..05c3f178 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSAttackComponent.h @@ -0,0 +1,74 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "Combat/RTSAttackData.h" + +#include "RTSAttackComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRTSAttackComponentCooldownReadySignature, AActor*, Actor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSAttackComponentAttackedUsedSignature, AActor*, Actor, const FRTSAttackData&, Attack, AActor*, Target, ARTSProjectile*, Projectile); + + +/** +* Adds one or more attacks to the actor. +* These can also be used for healing. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSAttackComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSAttackComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + /** Uses the passed attack on the specified target and starts the cooldown timer. */ + UFUNCTION(BlueprintCallable) + void UseAttack(int32 AttackIndex, AActor* Target); + + /** Gets the radius in which the actor will automatically select and attack targets, in cm. */ + UFUNCTION(BlueprintPure) + float GetAcquisitionRadius() const; + + /** Gets the radius around the home location of the actor it won't leave when automatically attacking targets, in cm. */ + UFUNCTION(BlueprintPure) + float GetChaseRadius() const; + + /** Gets the attacks available for the actor. Different attacks might be used at different ranges, or against different types of targets. */ + UFUNCTION(BlueprintPure) + TArray GetAttacks() const; + + /** Gets the time before the next attack can be used, in seconds. This is shared between attacks.*/ + UFUNCTION(BlueprintPure) + float GetRemainingCooldown() const; + + + /** Event when the attack cooldown has expired. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSAttackComponentCooldownReadySignature OnCooldownReady; + + /** Event when an actor has used an attack. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSAttackComponentAttackedUsedSignature OnAttackUsed; + +private: + /** Radius in which the actor will automatically select and attack targets, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float AcquisitionRadius; + + /** Radius around the home location of the actor it won't leave when automatically attacking targets, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float ChaseRadius; + + /** Attacks available for the actor. Different attacks might be used at different ranges, or against different types of targets. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray Attacks; + + /** Time before the next attack can be used, in seconds. This is shared between attacks.*/ + float RemainingCooldown; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSAttackData.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSAttackData.h new file mode 100644 index 00000000..92e321b4 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSAttackData.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/DamageType.h" +#include "Templates/SubclassOf.h" + +#include "Combat/RTSProjectile.h" + +#include "RTSAttackData.generated.h" + + +USTRUCT(BlueprintType) +struct REALTIMESTRATEGY_API FRTSAttackData +{ + GENERATED_BODY() + +public: + /** Time before this attack can be used again, in seconds. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float Cooldown; + + /** Damage dealt by this attack. Negative values could mean healing, depending on the game. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float Damage; + + /** Type of the damage caused by this attack. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + TSubclassOf DamageType; + + /** Range of this attack, in cm. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float Range; + + /** Type of the projectile to spawn. If not set, damage will be dealt instantly. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + TSubclassOf ProjectileClass; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSHealthBarWidgetComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSHealthBarWidgetComponent.h new file mode 100644 index 00000000..ab6b03ce --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSHealthBarWidgetComponent.h @@ -0,0 +1,35 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "UI/RTSActorWidgetComponent.h" + +#include "RTSHealthBarWidgetComponent.generated.h" + + +class URTSHealthComponent; + + +/** +* Adds a health bar widget to the actor. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API URTSHealthBarWidgetComponent : public URTSActorWidgetComponent +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + + /** Event when the current health of the actor has changed. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void UpdateHealthBar(float HealthPercentage); + + +private: + UPROPERTY() + URTSHealthComponent* HealthComponent; + + UFUNCTION() + void OnHealthChanged(AActor* Actor, float OldHealth, float NewHealth, AActor* DamageCauser); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSHealthComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSHealthComponent.h new file mode 100644 index 00000000..88d9df8d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSHealthComponent.h @@ -0,0 +1,58 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSHealthComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSHealthComponentHealthChangedSignature, AActor*, Actor, float, OldHealth, float, NewHealth, AActor*, DamageCauser); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSHealthComponentKilledSignature, AActor*, Actor, AController*, PreviousOwner, AActor*, DamageCauser); + + +/** +* Adds health to the actor, e.g. for taking damage and dying. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSHealthComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSHealthComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + virtual void BeginPlay() override; + + + /** Gets the maximum health of the actor. */ + UFUNCTION(BlueprintPure) + float GetMaximumHealth() const; + + /** Gets the current health of the actor. */ + UFUNCTION(BlueprintPure) + float GetCurrentHealth() const; + + + /** Event when the current health of the actor has changed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSHealthComponentHealthChangedSignature OnHealthChanged; + + /** Event when the actor has been killed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSHealthComponentKilledSignature OnKilled; + +private: + /** Maximum health of the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float MaximumHealth; + + /** Current health of the actor. */ + UPROPERTY(Replicated) + float CurrentHealth; + + UFUNCTION() + void OnTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSProjectile.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSProjectile.h new file mode 100644 index 00000000..0451143f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Combat/RTSProjectile.h @@ -0,0 +1,74 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Actor.h" +#include "Templates/SubclassOf.h" + +#include "RTSProjectile.generated.h" + + +class UProjectileMovementComponent; + + +/** +* Projectile with RTS features, such as following a target and dealing damage. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSProjectile : public AActor +{ + GENERATED_BODY() + +public: + ARTSProjectile(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + /** Locks on to the specified target, dealing damage on impact. */ + UFUNCTION(BlueprintCallable) + void FireAt( + AActor* ProjectileTarget, + float ProjectileDamage, + TSubclassOf ProjectileDamageType, + AController* ProjectileInstigator, + AActor* ProjectileDamageCauser); + + virtual void Tick(float DeltaSeconds) override; + + + /** Event when this projectile hits its target. */ + virtual void NotifyOnProjectileDetonated( + AActor* ProjectileTarget, + float ProjectileDamage, + TSubclassOf ProjectileDamageType, + AController* ProjectileInstigator, + AActor* ProjectileDamageCauser); + + /** Event when this projectile hits its target. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS", meta = (DisplayName = "OnProjectileDetonated")) + void ReceiveOnProjectileDetonated( + AActor* ProjectileTarget, + float ProjectileDamage, + TSubclassOf ProjectileDamageType, + AController* ProjectileInstigator, + AActor* ProjectileDamageCauser); + +private: + bool bFired; + + UPROPERTY() + AActor* Target; + + float Damage; + TSubclassOf DamageType; + + UPROPERTY() + AController* EventInstigator; + + UPROPERTY() + AActor* DamageCauser; + + float TimeToImpact; + + UPROPERTY(VisibleAnywhere, Category = "RTS") + UProjectileMovementComponent* ProjectileMovement; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSBuilderComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSBuilderComponent.h new file mode 100644 index 00000000..b887c1ee --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSBuilderComponent.h @@ -0,0 +1,98 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "RTSBuilderComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSBuilderComponentAssignedToConstructionSiteSignature, AActor*, Builder, AActor*, ConstructionSite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSBuilderComponentRemovedFromConstructionSiteSignature, AActor*, Builder, AActor*, ConstructionSite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSBuilderComponentConstructionSiteEnteredSignature, AActor*, Builder, AActor*, ConstructionSite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSBuilderComponentConstructionSiteLeftSignature, AActor*, Builder, AActor*, ConstructionSite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSBuilderComponentConstructionStartedSignature, AActor*, Builder, AActor*, ConstructionSite); + + +/** + * Allows the actor to construct buildings. + */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSBuilderComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + /** Assigns the builder to the specified construction site. */ + UFUNCTION(BlueprintCallable) + virtual void AssignToConstructionSite(AActor* ConstructionSite); + + /** Spawns a building of the specified type at the target location and assigns the builder. */ + UFUNCTION(BlueprintCallable) + virtual void BeginConstruction(TSubclassOf BuildingClass, const FVector& TargetLocation); + + /** Spawns a building of the specified type at the target location and assigns the builder. */ + UFUNCTION(BlueprintCallable) + void BeginConstructionByIndex(int32 BuildingIndex, const FVector& TargetLocation); + + /** Removes the builder from its assigned construction site. */ + UFUNCTION(BlueprintCallable) + virtual void LeaveConstructionSite(); + + + /** Gets the types of buildings the builder can construct. */ + UFUNCTION(BlueprintPure) + TArray> GetConstructibleBuildingClasses() const; + + /** Whether the builder enters the construction site while working on it, or not. */ + UFUNCTION(BlueprintPure) + bool DoesEnterConstructionSite() const; + + /** Gets the distance of the builder to the construction site while building. */ + UFUNCTION(BlueprintPure) + float GetConstructionSiteOffset() const; + + /** Gets the construction site the builder is currently working on. */ + UFUNCTION(BlueprintPure) + AActor* GetAssignedConstructionSite() const; + + + /** Event when the builder has been assigned to a construction site. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSBuilderComponentAssignedToConstructionSiteSignature OnAssignedToConstructionSite; + + /** Event when the builder is no longer assigned to a construction site. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSBuilderComponentRemovedFromConstructionSiteSignature OnRemovedFromConstructionSite; + + /** Event when the builder has entered a construction site. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSBuilderComponentConstructionSiteEnteredSignature OnConstructionSiteEntered; + + /** Event when the builder has left a construction site. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSBuilderComponentConstructionSiteLeftSignature OnConstructionSiteLeft; + + /** Event when the builder has created a new construction site. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSBuilderComponentConstructionSiteEnteredSignature OnConstructionStarted; + + +private: + /** Types of buildings the builder can construct. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> ConstructibleBuildingClasses; + + /** Whether the builder enters the construction site while working on it, or not. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + bool bEnterConstructionSite; + + /** Distance of the builder to the construction site while building. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float ConstructionSiteOffset; + + /** Construction site the builder is currently working on. */ + UPROPERTY() + AActor* AssignedConstructionSite; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSBuildingCursor.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSBuildingCursor.h new file mode 100644 index 00000000..eaa2521a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSBuildingCursor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "RTSBuildingCursor.generated.h" + +class USkeletalMesh; +class UStaticMesh; + +/** 3D cursor for selecting a location for a building. */ +UCLASS() +class REALTIMESTRATEGY_API ARTSBuildingCursor : public AActor +{ + GENERATED_BODY() + +public: + /** Sets the preview skeletal mesh of this cursor. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void SetSkeletalMesh(USkeletalMesh* BuildingMesh, const FTransform& RelativeTransform); + + /** Sets the preview static mesh of this cursor. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void SetStaticMesh(UStaticMesh* BuildingMesh, const FTransform& RelativeTransform); + + /** Applies the visuals for either a valid or an invalid location. */ + UFUNCTION(Category = RTS, BlueprintImplementableEvent) + void SetLocationValid(bool bValid); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionProgressBarWidgetComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionProgressBarWidgetComponent.h new file mode 100644 index 00000000..781e8d35 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionProgressBarWidgetComponent.h @@ -0,0 +1,29 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "UI/RTSActorWidgetComponent.h" + +#include "RTSConstructionProgressBarWidgetComponent.generated.h" + + +/** +* Adds a construction progress bar widget to the actor. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API URTSConstructionProgressBarWidgetComponent : public URTSActorWidgetComponent +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + + /** Event when the current construction progress of the actor has changed. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void UpdateConstructionProgressBar(float ProgressPercentage); + + +private: + UFUNCTION() + void OnConstructionProgressChanged(AActor* Actor, float ProgressPercentage); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionSiteComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionSiteComponent.h new file mode 100644 index 00000000..50ee1669 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionSiteComponent.h @@ -0,0 +1,198 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "Construction/RTSConstructionState.h" +#include "Economy/RTSPaymentType.h" +#include "Economy/RTSResourceType.h" + +#include "RTSConstructionSiteComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSConstructionSiteComponentConstructionStartedSignature, AActor*, ConstructionSite, float, TotalConstructionTime); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSConstructionSiteComponentConstructionProgressChangedSignature, AActor*, ConstructionSite, float, ProgressPercentage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRTSConstructionSiteComponentConstructionFinishedSignature, AActor*, ConstructionSite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRTSConstructionSiteComponentConstructionCanceledSignature, AActor*, ConstructionSite); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSConstructionSiteComponentBuilderConsumedSignature, AActor*, ConstructionSite, AActor*, Builder); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSConstructionSiteComponentConstructionCostRefundedSignature, AActor*, ConstructionSite, TSubclassOf, ResourceType, float, ResourceAmount); + + +/** Allows constructing the actor over time. */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSConstructionSiteComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSConstructionSiteComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + + /** Whether the specified builder can be assigned to this construction site. */ + UFUNCTION(BlueprintPure) + virtual bool CanAssignBuilder(AActor* Builder) const; + + /** Assigns the passed builder to this construction site, contributing to construction progress. */ + void AssignBuilder(AActor* Builder); + + /** Removes the passed builder from this construction site. */ + void UnassignBuilder(AActor* Builder); + + /** Gets the current construction progress [0..1]. */ + UFUNCTION(BlueprintPure) + float GetProgressPercentage() const; + + /** Whether the construction timer is currently being ticked, or not. */ + UFUNCTION(BlueprintPure) + bool IsConstructing() const; + + /** Whether the construction is finished and the actor ready to use. */ + UFUNCTION(BlueprintPure) + bool IsFinished() const; + + /** Starts constructing the actor, setting the timer. */ + UFUNCTION(BlueprintCallable) + virtual void StartConstruction(); + + /** Finishes constructing the actor. */ + UFUNCTION(BlueprintCallable) + virtual void FinishConstruction(); + + /** Cancels constructing the actor, destroying it. */ + UFUNCTION(BlueprintCallable) + virtual void CancelConstruction(); + + + /** When to pay resources for construction. */ + UFUNCTION(BlueprintPure) + ERTSPaymentType GetConstructionCostType() const; + + /** Gets the resources to pay for constructing the actor. */ + UFUNCTION(BlueprintPure) + TMap, float> GetConstructionCosts() const; + + /** Gets the time for constructing the actor, in seconds. */ + UFUNCTION(BlueprintPure) + float GetConstructionTime() const; + + /** Whether any builders working at this construction site are destroyed when finished. */ + UFUNCTION(BlueprintPure) + bool ConsumesBuilders() const; + + /** Gets how many builders may be assigned to this construction site at the same time. */ + UFUNCTION(BlueprintPure) + int32 GetMaxAssignedBuilders() const; + + /** Gets the factor to multiply all passed construction time with, independent of any currently assigned builders. */ + UFUNCTION(BlueprintPure) + float GetProgressMadeAutomatically() const; + + /** Gets the factor to multiply all passed construction time with, multiplied with the number of currently assigned builders. */ + UFUNCTION(BlueprintPure) + float GetProgressMadePerBuilder() const; + + /** Gets the resources to refund when canceling construction. */ + UFUNCTION(BlueprintPure) + float GetRefundFactor() const; + + /** Whether to start construction immediately after spawn, or not. */ + UFUNCTION(BlueprintPure) + bool DoesStartImmediately() const; + + /** Whether the construction timer is currently being ticked, or not. */ + UFUNCTION(BlueprintPure) + ERTSConstructionState GetState() const; + + /** Gets the time before the actor is constructed, in seconds. */ + UFUNCTION(BlueprintPure) + float GetRemainingConstructionTime() const; + + /** Gets the builders currently working at this construction site. */ + UFUNCTION(BlueprintPure) + TArray GetAssignedBuilders() const; + + + /** Event when the construction timer has been started. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSConstructionSiteComponentConstructionStartedSignature OnConstructionStarted; + + /** Event when the construction timer has been updated. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSConstructionSiteComponentConstructionProgressChangedSignature OnConstructionProgressChanged; + + /** Event when the construction timer has expired. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSConstructionSiteComponentConstructionFinishedSignature OnConstructionFinished; + + /** Event when the construction has been canceled and the construction site destroyed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSConstructionSiteComponentConstructionCanceledSignature OnConstructionCanceled; + + /** Event when a builder working at this construction site has been destroyed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSConstructionSiteComponentBuilderConsumedSignature OnBuilderConsumed; + + /** Event when any construction costs have been refunded. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSConstructionSiteComponentConstructionCostRefundedSignature OnConstructionCostRefunded; + +private: + /** When to pay resources for construction. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + ERTSPaymentType ConstructionCostType; + + /** Resources to pay for constructing the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TMap, float> ConstructionCosts; + + /** Time for constructing the actor, in seconds. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float ConstructionTime; + + /** Whether any builders working at this construction site are destroyed when finished. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + bool bConsumesBuilders; + + /** How many builders may be assigned to this construction site at the same time. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + int32 MaxAssignedBuilders; + + /** Factor to multiply all passed construction time with, independent of any currently assigned builders. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float ProgressMadeAutomatically; + + /** Factor to multiply all passed construction time with, multiplied with the number of currently assigned builders. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float ProgressMadePerBuilder; + + /** Resources to refund when canceling construction. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float RefundFactor; + + /** Whether to start construction immediately after spawn, or not. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + bool bStartImmediately; + + /** Whether the construction timer is currently being ticked, or not. */ + UPROPERTY(EditInstanceOnly, Replicated, Category = "RTS") + ERTSConstructionState State; + + /** Time before the actor is constructed, in seconds. */ + UPROPERTY(ReplicatedUsing=ReceivedRemainingConstructionTime) + float RemainingConstructionTime; + + /** Builders currently working at this construction site. */ + UPROPERTY() + TArray AssignedBuilders; + + UFUNCTION() + void ReceivedRemainingConstructionTime(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionState.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionState.h new file mode 100644 index 00000000..53e46f0b --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Construction/RTSConstructionState.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "RTSConstructionState.generated.h" + + +UENUM(BlueprintType) +enum class ERTSConstructionState : uint8 +{ + /** Construction has not yet started. */ + CONSTRUCTIONSTATE_NotStarted, + + /** Building is being constructed right now. */ + CONSTRUCTIONSTATE_Constructing, + + /** Construction timer is paused. */ + CONSTRUCTIONSTATE_Paused, + + /** Construction has finished. */ + CONSTRUCTIONSTATE_Finished +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSGatherData.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSGatherData.h new file mode 100644 index 00000000..38c854e5 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSGatherData.h @@ -0,0 +1,41 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Templates/SubclassOf.h" + +#include "Economy/RTSResourceType.h" + +#include "RTSGatherData.generated.h" + + +USTRUCT(BlueprintType) +struct REALTIMESTRATEGY_API FRTSGatherData +{ + GENERATED_BODY() + +public: + /** Type of resources that can be gathered. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + TSubclassOf ResourceType; + + /** Amount of resources gathered after each cooldown. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float AmountPerGathering; + + /** Maximum amount of resources that can be carried. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float Capacity; + + /** Time between two gatherings, in seconds. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float Cooldown; + + /** Whether the actor needs to return to the drain in order to drop resources. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + bool bNeedsReturnToDrain; + + /** Range in which resources can be gathered, in cm. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RTS") + float Range; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSGathererComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSGathererComponent.h new file mode 100644 index 00000000..d4429029 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSGathererComponent.h @@ -0,0 +1,128 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "Economy/RTSGatherData.h" + +#include "RTSGathererComponent.generated.h" + + +class AActor; +class URTSResourceType; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSGathererComponentResourcesGatheredSignature, AActor*, Gatherer, AActor*, ResourceSource, const FRTSGatherData&, GatherData, float, GatheredAmount); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSGathererComponentResourcesReturnedSignature, AActor*, Gatherer, AActor*, ResourceDrain, TSubclassOf, ResourceType, float, ReturnedAmount); + + +/** +* Allows the actor to gather resources. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSGathererComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSGathererComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + + /** Checks whether the actor can gather from the specified source, e.g. is allowed to gather, and is not at capacity limit. */ + UFUNCTION(BlueprintPure) + virtual bool CanGatherFrom(AActor* ResourceSource) const; + + /** Finds the closest resource drain for returning currently carried resources. */ + UFUNCTION(BlueprintPure) + virtual AActor* FindClosestResourceDrain() const; + + /** Gets the resource source the actor has recently been gathering from, if available, or a similar one within its sweep radius. */ + UFUNCTION(BlueprintPure) + virtual AActor* GetPreferredResourceSource() const; + + /** Gets the closest resource source of the specified type within the passed maximum distance around the actor (0 means anywhere). */ + UFUNCTION(BlueprintPure) + virtual AActor* GetClosestResourceSource(TSubclassOf DesiredResourceType, float MaxDistance) const; + + /** Gets the maximum distance for gathering resources from the specified source. */ + UFUNCTION(BlueprintPure) + virtual float GetGatherRange(AActor* ResourceSource) const; + + /** Whether the gatherer is currently carrying any resources that could be returned. */ + UFUNCTION(BlueprintPure) + bool IsCarryingResources() const; + + /** Whether this gatherer is currently gathering resources. */ + UFUNCTION(BlueprintPure) + bool IsGathering() const; + + /** Starts the cooldown timer for gathering resources from the specified source. */ + UFUNCTION(BlueprintCallable) + virtual void StartGatheringResources(AActor* ResourceSource); + + /** Gathers resources from the specified source and starts the cooldown timer. */ + UFUNCTION(BlueprintCallable) + virtual float GatherResources(AActor* ResourceSource); + + /** Returns resources to the specified drain. */ + UFUNCTION(BlueprintCallable) + virtual float ReturnResources(AActor* ResourceDrain); + + /** Gets the before the next resources are gathered, in seconds. */ + UFUNCTION(BlueprintPure) + float GetRemainingCooldown() const; + + /** Event when the actor has gathered resources from a source. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSGathererComponentResourcesGatheredSignature OnResourcesGathered; + + /** Event when the actor has returned resources to a drain. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSGathererComponentResourcesReturnedSignature OnResourcesReturned; + +private: + /** Resources that can be gathered by the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray GatheredResources; + + /** Types of actors the gatherer can gather resources from. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> ResourceSourceActorClasses; + + /** Radius in which the actor will automatically gather resources from, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float ResourceSweepRadius; + + /** Amount of resources the actor is carrying. */ + UPROPERTY(Replicated) + float CarriedResourceAmount; + + /** Type of resource the actor is carrying. */ + UPROPERTY(Replicated) + TSubclassOf CarriedResourceType; + + /** Resource source the actor is currently gathering from .*/ + UPROPERTY() + AActor* CurrentResourceSource; + + /** Resource source the actor has been gathering from before.*/ + UPROPERTY() + AActor* PreviousResourceSource; + + /** Time before the next resources are gathered, in seconds. */ + float RemainingCooldown; + + /** Type of resource gathered before. */ + TSubclassOf PreviousResourceType; + + + bool GetGatherDataForResourceSource(AActor* ResourceSource, FRTSGatherData* OutGatherData) const; + bool GetGatherDataForResourceType(TSubclassOf ResourceType, FRTSGatherData* OutGatherData) const; + void LeaveCurrentResourceSource(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSPaymentType.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSPaymentType.h new file mode 100644 index 00000000..62a5fc9c --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSPaymentType.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "RTSPaymentType.generated.h" + + +UENUM(BlueprintType) +enum class ERTSPaymentType : uint8 +{ + /** Pay whole production cost as soon as production starts. */ + PAYMENT_PayImmediately, + + /** Pay production costs every tick while production proceeds. */ + PAYMENT_PayOverTime, + + /** Don't pay production costs automatically. */ + PAYMENT_PayCustom +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSPlayerResourcesComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSPlayerResourcesComponent.h new file mode 100644 index 00000000..ac2fdabd --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSPlayerResourcesComponent.h @@ -0,0 +1,74 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "Economy/RTSResourceType.h" + +#include "RTSPlayerResourcesComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSPlayerResourcesComponentResourcesChangedSignature, TSubclassOf, ResourceType, float, OldResourceAmount, float, NewResourceAmount, bool, bSyncedFromServer); + + +/** +* Attach to player or AI controllers. Allows the controller to store resources. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSPlayerResourcesComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSPlayerResourcesComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + void BeginPlay() override; + + + /** Gets the amount of resources in stock of this player. */ + UFUNCTION(BlueprintPure) + float GetResources(TSubclassOf ResourceType) const; + + /** Gets the types of resources available to this player. */ + TArray> GetResourceTypes() const; + + /** Checks the amount of resources in stock of this player. */ + UFUNCTION(BlueprintPure) + bool CanPayResources(TSubclassOf ResourceType, float ResourceAmount) const; + + /** Checks the amount of resources in stock of this player. */ + UFUNCTION(BlueprintPure) + bool CanPayAllResources(TMap, float> Resources) const; + + /** Adds the specified resources to the stock of this player. */ + UFUNCTION(BlueprintCallable) + virtual float AddResources(TSubclassOf ResourceType, float ResourceAmount); + + /** Removes the specified resources from the stock of this player. */ + UFUNCTION(BlueprintCallable) + virtual float PayResources(TSubclassOf ResourceType, float ResourceAmount); + + /** Removes the specified resources from the stock of this player. */ + UFUNCTION(BlueprintCallable) + virtual void PayAllResources(TMap, float> Resources); + + + /** Event when the current resource stock amount for the player has changed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSPlayerResourcesComponentResourcesChangedSignature OnResourcesChanged; + +private: + /** Types of resources currently available to this player. Num must match ResourceAmounts. Need to use an array here instead of map for replication. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Resources") + TArray> ResourceTypes; + + /** Resources currently available to this player. Num must match ResourceTypes. Need to use an array here instead of map for replication. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Resources", ReplicatedUsing = ReceivedResourceAmounts) + TArray ResourceAmounts; + + UFUNCTION() + void ReceivedResourceAmounts(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceDrainComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceDrainComponent.h new file mode 100644 index 00000000..a5fed78d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceDrainComponent.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "Economy/RTSResourceType.h" + +#include "RTSResourceDrainComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSResourceDrainComponentResourcesReturnedSignature, AActor*, ResourceDrain, AActor*, Gatherer, TSubclassOf, ResourceType, float, ResourceAmount); + + +/** +* Allows resources to be returned to the actor. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSResourceDrainComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSResourceDrainComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + /** Returns resources to this actor, notifying the owning player. */ + virtual float ReturnResources(AActor* Gatherer, TSubclassOf ResourceType, float ResourceAmount); + + + /** Gets the types of resources that can be returned to the actor. */ + UFUNCTION(BlueprintPure) + TArray> GetResourceTypes() const; + + /** Whether gatherers must enter the resource drain for returning resources. */ + UFUNCTION(BlueprintPure) + bool MustGathererEnter() const; + + /** Gets how many gatherers may enter at the same time. */ + UFUNCTION(BlueprintPure) + int32 GetGathererCapacity() const; + + + /** Event when resources have been returned to the actor. */ + UFUNCTION(NetMulticast, reliable) + virtual void NotifyOnResourcesReturned(AActor* Gatherer, TSubclassOf ResourceType, float ResourceAmount); + + + /** Event when resources have been returned to the actor. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSResourceDrainComponentResourcesReturnedSignature OnResourcesReturned; + +private: + /** Types of resources that can be returned to the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> ResourceTypes; + + /** Whether gatherers must enter the resource drain for returning resources. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + bool bGathererMustEnter; + + /** How many gatherers may enter at the same time. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 1)) + int32 GathererCapacity; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceSourceComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceSourceComponent.h new file mode 100644 index 00000000..6ca3af87 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceSourceComponent.h @@ -0,0 +1,98 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "Economy/RTSResourceType.h" + +#include "RTSResourceSourceComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSResourceSourceComponentResourcesChangedSignature, AActor*, ResourceSource, float, OldResources, float, NewResources); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRTSResourceSourceComponentDepletedSignature, AActor*, ResourceSource); + + +/** +* Adds resources to be gathered from the actor. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSResourceSourceComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSResourceSourceComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + virtual void BeginPlay() override; + + + /** Extracts resources from this actor, applying gathering factor and checking remaining amount. */ + UFUNCTION(BlueprintCallable) + virtual float ExtractResources(AActor* Gatherer, float ResourceAmount); + + /** Checks whether the specified gatherer can enter the resource source right now. */ + UFUNCTION(BlueprintPure) + virtual bool CanGathererEnter(AActor* Gatherer) const; + + + /** Gets type of resources to be gathered from the actor. */ + UFUNCTION(BlueprintPure) + TSubclassOf GetResourceType() const; + + /** Gets the maximum resources available at the actor. */ + UFUNCTION(BlueprintPure) + float GetMaximumResources() const; + + /** Gets the factor to multiply all gathered resources with (e.g. very abundant resource nodes. */ + UFUNCTION(BlueprintPure) + float GetGatheringFactor() const; + + /** Whether gatherers must enter the resource source for gathering. */ + UFUNCTION(BlueprintPure) + bool MustGathererEnter() const; + + /** Gets how many gatherers may enter at the same time. */ + UFUNCTION(BlueprintPure) + int32 GetGathererCapacity() const; + + /** Gets the current resources available at the actor. */ + UFUNCTION(BlueprintPure) + float GetCurrentResources() const; + + + /** Event when the amount of available resources has changed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSResourceSourceComponentResourcesChangedSignature OnResourcesChanged; + + /** Event when the actor has been depleted. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSResourceSourceComponentDepletedSignature OnDepleted; + +private: + /** Type of resources to be gathered from the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TSubclassOf ResourceType; + + /** Maximum resources available at the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float MaximumResources; + + /** Factor to multiply all gathered resources with (e.g. very abundant resource nodes. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float GatheringFactor; + + /** Whether gatherers must enter the resource source for gathering. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + bool bGathererMustEnter; + + /** How many gatherers may enter at the same time. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + int32 GathererCapacity; + + /** Current resources available at the actor. */ + UPROPERTY(Replicated) + float CurrentResources; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceType.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceType.h new file mode 100644 index 00000000..608d2596 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Economy/RTSResourceType.h @@ -0,0 +1,13 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "RTSResourceType.generated.h" + + +/** Resource that can be gathered and spent for construction, production and research. */ +UCLASS(Blueprintable, BlueprintType) +class REALTIMESTRATEGY_API URTSResourceType : public UObject +{ + GENERATED_BODY() +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSCollisionLibrary.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSCollisionLibrary.h new file mode 100644 index 00000000..338a89f1 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSCollisionLibrary.h @@ -0,0 +1,63 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "GameFramework/Actor.h" +#include "Templates/SubclassOf.h" + +#include "RTSCollisionLibrary.generated.h" + + +class UObject; +class UShapeComponent; + + +/** +* Utility functions for calculating actor collisions. +*/ +UCLASS() +class REALTIMESTRATEGY_API URTSCollisionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Gets the distance between the two specified actors, optionally subtracting their collision sizes. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetActorDistance(AActor* First, AActor* Second, bool bConsiderCollisionSize); + + /** Gets the approximated collision size of actors of the specified class. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetCollisionSize(TSubclassOf ActorClass); + + /** Gets the approximated collision height of actors of the specified class. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetCollisionHeight(TSubclassOf ActorClass); + + /** Gets the approximated collision size of the specified actor. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetActorCollisionSize(AActor* Actor); + + /** Gets the approximated collision height of the specified actor. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetActorCollisionHeight(AActor* Actor); + + /** Gets the approximated collision size of the specified shape. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetShapeCollisionSize(UShapeComponent* ShapeComponent); + + /** Gets the approximated collision height of the specified shape. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static float GetShapeCollisionHeight(UShapeComponent* ShapeComponent); + + /** Casts a ray to determine the z coordinate of the specified location on ground level. */ + UFUNCTION(BlueprintPure, Category = "RTS", meta = (WorldContext = "WorldContextObject")) + static FVector GetGroundLocation(UObject* WorldContextObject, FVector Location); + + /** + * Checks whether the specified actor can be placed at the passed location. + */ + UFUNCTION(BlueprintPure, Category = "RTS") + static bool IsSuitableLocationForActor(UWorld* World, TSubclassOf ActorClass, const FVector& Location); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSGameplayLibrary.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSGameplayLibrary.h new file mode 100644 index 00000000..a2848e9c --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSGameplayLibrary.h @@ -0,0 +1,51 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "Components/ActorComponent.h" +#include "GameFramework/Actor.h" +#include "Templates/SubclassOf.h" + +#include "RTSGameplayLibrary.generated.h" + +/** +* Utility functions for gameplay calculations. +*/ +UCLASS() +class REALTIMESTRATEGY_API URTSGameplayLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Searches the components attached to the specified actor class and returns the first encountered component of the specified class. */ + template + static T* FindDefaultComponentByClass(const TSubclassOf InActorClass) + { + return (T*)FindDefaultComponentByClass(InActorClass, T::StaticClass()); + } + + /** Searches the components attached to the specified actor class and returns the first encountered component of the specified class. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static UActorComponent* FindDefaultComponentByClass(const TSubclassOf InActorClass, const TSubclassOf InComponentClass); + + /** Checks whether the specified actor is owned by an AI player. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static bool IsAIUnit(AActor* Actor); + + /** Checks whether the specified actor is ready to use (e.g. finished construction). */ + UFUNCTION(BlueprintPure, Category = "RTS") + static bool IsReadyToUse(AActor* Actor); + + /** Checks if the owner of the specified actor meets all requirements for producing the desired new actor. */ + UFUNCTION(BlueprintPure, Category = "RTS", meta = (WorldContext = "WorldContextObject")) + static bool OwnerMeetsAllRequirementsFor(UObject* WorldContextObject, AActor* OwnedActor, TSubclassOf DesiredProduct); + + /** Checks if the owner of the specified actor meets all requirements for producing the desired new actor. */ + static bool GetMissingRequirementFor(UObject* WorldContextObject, AActor* OwnedActor, TSubclassOf DesiredProduct, TSubclassOf& OutMissingRequirement); + +private: + /** Helper function - check if owner is a bot */ + static bool IsOwnerABot(class URTSOwnerComponent* OwnerComponent); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSGameplayTagLibrary.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSGameplayTagLibrary.h new file mode 100644 index 00000000..eda52ea7 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Libraries/RTSGameplayTagLibrary.h @@ -0,0 +1,26 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "GameplayTagContainer.h" + +#include "RTSGameplayTagLibrary.generated.h" + +/** +* Utility functions for adding, checking and removing gameplay tags. +*/ +UCLASS() +class REALTIMESTRATEGY_API URTSGameplayTagLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Searches the components attached to the specified actor class and returns the first encountered component of the specified class. */ + UFUNCTION(BlueprintPure, Category = "RTS") + static bool HasGameplayTag(const AActor* Actor, const FGameplayTag& Tag); + + /** Actor can be attacked by other actors. */ + static const FGameplayTag& Status_Permanent_CanBeAttacked(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionComponent.h new file mode 100644 index 00000000..6272016d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionComponent.h @@ -0,0 +1,141 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "Production/RTSProductionQueue.h" + +#include "RTSProductionComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSProductionComponentProductQueuedSignature, AActor*, Actor, TSubclassOf, ProductClass, int32, QueueIndex); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSProductionComponentProductionStartedSignature, AActor*, Actor, TSubclassOf, ProductClass, int32, QueueIndex, float, TotalProductionTime); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSProductionComponentProductionProgressChangedSignature, AActor*, Actor, int32, QueueIndex, float, ProgressPercentage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSProductionComponentProductionFinishedSignature, AActor*, Actor, AActor*, Product, int32, QueueIndex); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FRTSProductionComponentProductionCanceledSignature, AActor*, Actor, TSubclassOf, ProductClass, int32, QueueIndex, float, ElapsedProductionTime); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FRTSProductionComponentProductionCostRefundedSignature, AActor*, Actor, TSubclassOf, ResourceType, float, ResourceAmount); + + +/** Allows producing actors over time. */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSProductionComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSProductionComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + + /** Checks whether the actor can start producing the specified product. */ + UFUNCTION(BlueprintPure) + virtual bool CanAssignProduction(TSubclassOf ProductClass) const; + + /** Gets the index of the queue the specified product would be assigned to, or -1 all queues are at the capacity limit. */ + UFUNCTION(BlueprintPure) + virtual int32 FindQueueForProduct(TSubclassOf ProductClass) const; + + /** Gets the product currently being produced in the specified queue. */ + UFUNCTION(BlueprintPure) + TSubclassOf GetCurrentProduction(int32 QueueIndex = 0) const; + + /** Gets the required time for producing the current product in the specified queue. */ + UFUNCTION(BlueprintPure) + float GetProductionTime(int32 QueueIndex = 0) const; + + /** Gets the required time for producing the specified product. */ + UFUNCTION(BlueprintPure) + virtual float GetProductionTimeForProduct(TSubclassOf ProductClass) const; + + /** Gets the current production progress [0..1] for the specified queue. */ + UFUNCTION(BlueprintPure) + float GetProgressPercentage(int32 QueueIndex = 0) const; + + /** Gets the time before producing the current product in the specified queue has finished. */ + UFUNCTION(BlueprintPure) + float GetRemainingProductionTime(int32 QueueIndex = 0) const; + + /** Whether any queue production timer is currently being ticked, or not. */ + UFUNCTION(BlueprintPure) + bool IsProducing() const; + + /** Starts producing the specified product, setting the timer. */ + UFUNCTION(BlueprintCallable) + virtual void StartProduction(TSubclassOf ProductClass); + + /** Finishes producing the product in the specified queue. */ + UFUNCTION(BlueprintCallable) + virtual void FinishProduction(int32 QueueIndex = 0); + + /** Cancels producing the product in the specified queue. */ + UFUNCTION(BlueprintCallable) + virtual void CancelProduction(int32 QueueIndex = 0, int32 ProductIndex = 0); + + + /** Gets the types of actors the actor can produce. */ + UFUNCTION(BlueprintPure) + TArray> GetAvailableProducts() const; + + /** Gets how many products can be produced simultaneously. */ + UFUNCTION(BlueprintPure) + int32 GetQueueCount() const; + + /** Gets how many products may be queued per queue. */ + UFUNCTION(BlueprintPure) + int32 GetCapacityPerQueue() const; + + + /** Event when a product has been queued for production. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSProductionComponentProductQueuedSignature OnProductQueued; + + /** Event when the production timer has been started. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSProductionComponentProductionStartedSignature OnProductionStarted; + + /** Event when the production timer has been updated. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSProductionComponentProductionProgressChangedSignature OnProductionProgressChanged; + + /** Event when the production timer has expired. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSProductionComponentProductionFinishedSignature OnProductionFinished; + + /** Event when the production has been canceled. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSProductionComponentProductionCanceledSignature OnProductionCanceled; + + /** Event when any production costs have been refunded. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSProductionComponentProductionCostRefundedSignature OnProductionCostRefunded; + +private: + /** Types of actors the actor can produce. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> AvailableProducts; + + /** How many products can be produced simultaneously. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 1)) + int32 QueueCount; + + /** How many products may be queued per queue. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 1)) + int32 CapacityPerQueue; + + /** Products queued for production. */ + UPROPERTY(ReplicatedUsing=ReceivedProductionQueues) + TArray ProductionQueues; + + void DequeueProduct(int32 QueueIndex = 0, int32 ProductIndex = 0); + void StartProductionInQueue(int32 QueueIndex = 0); + + UFUNCTION() + void ReceivedProductionQueues(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionCostComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionCostComponent.h new file mode 100644 index 00000000..cf8b7e22 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionCostComponent.h @@ -0,0 +1,57 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "Economy/RTSPaymentType.h" +#include "Economy/RTSResourceType.h" + +#include "RTSProductionCostComponent.generated.h" + + +/** Specifies the time and resources required to construct the actor. */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSProductionCostComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSProductionCostComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + /** When to pay resources for production. */ + UFUNCTION(BlueprintPure) + ERTSPaymentType GetProductionCostType() const; + + /** Gets the tme for producing the actor, in seconds. */ + UFUNCTION(BlueprintPure) + float GetProductionTime() const; + + /** Gets the resources to pay for producing the actor. */ + UFUNCTION(BlueprintPure) + TMap, float> GetResources() const; + + /** Gets the resources to refund when canceling production. */ + UFUNCTION(BlueprintPure) + float GetRefundFactor() const; + + +private: + /** When to pay resources for production. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + ERTSPaymentType ProductionCostType; + + /** Time for producing the actor, in seconds. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float ProductionTime; + + /** Resources to pay for producing the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TMap, float> Resources; + + /** Resources to refund when canceling production. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float RefundFactor; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionProgressBarWidgetComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionProgressBarWidgetComponent.h new file mode 100644 index 00000000..1d6d4290 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionProgressBarWidgetComponent.h @@ -0,0 +1,29 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "UI/RTSActorWidgetComponent.h" + +#include "RTSProductionProgressBarWidgetComponent.generated.h" + + +/** +* Adds a production progress bar widget to the actor. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API URTSProductionProgressBarWidgetComponent : public URTSActorWidgetComponent +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + + /** Event when the current production progress of the actor has changed. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void UpdateProductionProgressBar(int32 QueueIndex, float ProgressPercentage); + + +private: + UFUNCTION() + void OnProductionProgressChanged(AActor* Actor, int32 QueueIndex, float ProgressPercentage); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionQueue.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionQueue.h new file mode 100644 index 00000000..663d49c5 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Production/RTSProductionQueue.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Actor.h" +#include "Templates/SubclassOf.h" + +#include "RTSProductionQueue.generated.h" + + +USTRUCT(BlueprintType) +struct REALTIMESTRATEGY_API FRTSProductionQueue +{ + GENERATED_BODY() + +public: + /** Products queued for production. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTS") + TArray> Queue; + + /** Time before the current actor in the queue is produced, in seconds. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTS") + float RemainingProductionTime; + + + /** Gets the product with the specified index in this queue. */ + TSubclassOf operator[](int32 Index) const; + + + /** Enqueues the specified product. */ + void Add(TSubclassOf Product); + + /** Gets the number of products in this queue. */ + int32 Num() const; + + /** Removes the product with the specified index in this queue, advancing all later products. */ + void RemoveAt(int32 Index); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSCameraBoundsVolume.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSCameraBoundsVolume.h new file mode 100644 index 00000000..515a555a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSCameraBoundsVolume.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Volume.h" + +#include "RTSCameraBoundsVolume.generated.h" + + +/** + * Volume that restricts RTS camera movement. + * RTS cameras are not allowed to move outside of this volume. + * There should never be more than one camera bounds volume per level. + */ +UCLASS() +class REALTIMESTRATEGY_API ARTSCameraBoundsVolume : public AVolume +{ + GENERATED_BODY() +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSCheatManager.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSCheatManager.h new file mode 100644 index 00000000..71ad443c --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSCheatManager.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/CheatManager.h" +#include "Templates/SubclassOf.h" + +#include "RTSCheatManager.generated.h" + + +class URTSResourceType; + + +UCLASS(Blueprintable, Within = PlayerController) +class REALTIMESTRATEGY_API URTSCheatManager : public UCheatManager +{ + GENERATED_BODY() + +public: + URTSCheatManager(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + /** Increases construction and production speed. */ + UFUNCTION(exec, BlueprintCallable, Category = "Cheat Manager") + virtual void Boost(); + + /** Invulnerability cheat. */ + virtual void God() override; + + /** Adds resources. */ + UFUNCTION(exec, BlueprintCallable, Category = "Cheat Manager") + virtual void Money(); + + /** Defeat all other players. */ + UFUNCTION(exec, BlueprintCallable, Category = "Cheat Manager") + virtual void Victory(); + +private: + /** Amount of resources to grant with the Money cheat. */ + UPROPERTY(EditDefaultsOnly, Category = "Cheat Manager") + float ResourceAmount; + + /** Types of resources to grant with the Money cheat. */ + UPROPERTY(EditDefaultsOnly, Category = "Cheat Manager") + TArray> ResourceTypes; + + /** Factor to multiply all construction and production speed with. */ + UPROPERTY(EditDefaultsOnly, Category = "Cheat Manager") + float SpeedBoostFactor; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSContainerComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSContainerComponent.h new file mode 100644 index 00000000..d8b97f2b --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSContainerComponent.h @@ -0,0 +1,65 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSContainerComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSContainerComponentActorEnteredSignature, AActor*, Container, AActor*, Actor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSContainerComponentActorLeftSignature, AActor*, Container, AActor*, Actor); + + +/** Can hold one or more actors. */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSContainerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSContainerComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + /** Whether the specified actor can enter this container. */ + UFUNCTION(BlueprintPure) + virtual bool CanLoadActor(AActor* Actor) const; + + /** Adds the specified actor to this container. */ + UFUNCTION(BlueprintCallable) + virtual void LoadActor(AActor* Actor); + + /** Removes the specified actor from this container. */ + UFUNCTION(BlueprintCallable) + virtual void UnloadActor(AActor* Actor); + + + /** Gets how many actors may enter this container at the same time. Negative number means unlimited capacity, or will be set elsewhere. */ + UFUNCTION(BlueprintPure) + int32 GetCapacity() const; + + /** Sets how many actors may enter this container at the same time. Negative number means unlimited capacity, or will be set elsewhere. */ + void SetCapacity(int32 InCapacity); + + /** Gets the actors currently held by this container. */ + UFUNCTION(BlueprintPure) + TArray GetContainedActors() const; + + + /** Event when a new actor has entered this container. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSContainerComponentActorEnteredSignature OnActorEntered; + + /** Event when an actor has left this container. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSContainerComponentActorLeftSignature OnActorLeft; + +private: + /** How many actors may enter this container at the same time. Negative number means unlimited capacity, or will be set elsewhere. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + int32 Capacity; + + /** Actors currently held by this container. */ + UPROPERTY() + TArray ContainedActors; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSControlGroup.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSControlGroup.h new file mode 100644 index 00000000..f6f66b40 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSControlGroup.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Actor.h" + +#include "RTSControlGroup.generated.h" + +/** Group of actors. */ +USTRUCT(BlueprintType) +struct REALTIMESTRATEGY_API FRTSControlGroup +{ + GENERATED_BODY() + +public: + /** Actors of this control group. */ + UPROPERTY() + TArray Actors; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSDescriptionComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSDescriptionComponent.h new file mode 100644 index 00000000..2eaa1200 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSDescriptionComponent.h @@ -0,0 +1,27 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSDescriptionComponent.generated.h" + + +/** +* Adds a localizable description to the actor. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSDescriptionComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + /** Gets the description of the actor. */ + UFUNCTION(BlueprintPure) + FText GetDescription() const; + +private: + /** Description of the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + FText Description; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameMode.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameMode.h new file mode 100644 index 00000000..c751478a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameMode.h @@ -0,0 +1,93 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/GameModeBase.h" +#include "Templates/SubclassOf.h" + +#include "RTSGameMode.generated.h" + + +class AController; + +class ARTSPlayerAIController; +class ARTSPlayerController; +class ARTSPlayerStart; +class ARTSTeamInfo; + + +/** +* Game mode with RTS features, such as spawning initial units for each player. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + ARTSGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + virtual void BeginPlay() override; + virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; + + ARTSPlayerStart* FindRTSPlayerStart(AController* Player); + + virtual void RestartPlayer(AController* NewPlayer) override; + virtual void RestartPlayerAtPlayerStart(AController* NewPlayer, AActor* StartSpot) override; + + virtual ARTSPlayerAIController* StartAIPlayer(); + + /** Spawns an actor of the specified type and transfers ownership to the specified player. */ + virtual AActor* SpawnActorForPlayer(TSubclassOf ActorClass, AController* ActorOwner, const FTransform& SpawnTransform); + + /** Sets the specified player as the owner of the passed actor. */ + UFUNCTION(BlueprintCallable) + void TransferOwnership(AActor* Actor, AController* NewOwner); + + /** Event when an actor has been killed. */ + virtual void NotifyOnActorKilled(AActor* Actor, AController* ActorOwner); + + /** Event when a player has been defeated. */ + virtual void NotifyOnPlayerDefeated(AController* Player); + + /** Event when a player has been defeated. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS", meta = (DisplayName = "OnPlayerDefeated")) + void ReceiveOnPlayerDefeated(AController* Player); + +private: + /** Actors to spawn for each player in the game. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> InitialActors; + + /** Relative locations of the actors to spawn for each player in the game, relative to their respective start spot. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray InitialActorLocations; + + /** Optional types of actors that are required for a player to be alive. As soon as no actor of the specified type is alive, the player is defeated. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> DefeatConditionActorClasses; + + /** Class of TeamInfo to spawn. */ + UPROPERTY(EditDefaultsOnly, Category = "Team") + TSubclassOf TeamClass; + + /** Number of teams to create. */ + UPROPERTY(EditDefaultsOnly, Category = "Team", meta = (ClampMin = 0)) + uint8 NumTeams; + + /** AIController class to spawn for AI players. */ + UPROPERTY(EditDefaultsOnly, Category = AI) + TSubclassOf PlayerAIControllerClass; + + /** Number of AI players to spawn. */ + UPROPERTY(EditDefaultsOnly, Category = AI) + int32 NumAIPlayers; + + /** Teams of the current match. */ + UPROPERTY() + TArray Teams; + + /** Gets the first player index that isn't assigned to any player. */ + uint8 GetAvailablePlayerIndex(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameState.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameState.h new file mode 100644 index 00000000..031aadf4 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameState.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/GameStateBase.h" + +#include "RTSGameState.generated.h" + + +/** +* Game state with RTS features. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSGameState : public AGameStateBase +{ + GENERATED_BODY() + +public: + virtual void HandleBeginPlay() override; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameplayTagsComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameplayTagsComponent.h new file mode 100644 index 00000000..e1f7dfc9 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSGameplayTagsComponent.h @@ -0,0 +1,71 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "GameplayTagAssetInterface.h" +#include "GameplayTagContainer.h" + +#include "RTSGameplayTagsComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSGameplayTagsComponentCurrentTagsChangedSignature, AActor*, Actor, FGameplayTagContainer, CurrentTags); + + +/** +* Allows tagging an actor with various properties. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSGameplayTagsComponent : public UActorComponent, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + URTSGameplayTagsComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + virtual void BeginPlay() override; + + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + + /** Gets the current set of gameplay tags of the actor. */ + FGameplayTagContainer GetCurrentTags() const; + + /** Adds the specified gameplay tag to the actor. */ + UFUNCTION(BlueprintCallable) + void AddGameplayTag(const FGameplayTag& NewTag); + + /** Adds the specified gameplay tags to the actor. */ + UFUNCTION(BlueprintCallable) + void AddGameplayTags(const FGameplayTagContainer& NewTags); + + /** Removes the specified gameplay tag from the actor. */ + UFUNCTION(BlueprintCallable) + bool RemoveGameplayTag(const FGameplayTag& TagToRemove); + + /** Removes the specified gameplay tags from the actor. */ + UFUNCTION(BlueprintCallable) + void RemoveGameplayTags(const FGameplayTagContainer& TagsToRemove); + + + /** Event when the current gameplay tags of the actor have changed. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSGameplayTagsComponentCurrentTagsChangedSignature CurrentTagsChanged; + +private: + /** Tags to add on BeginPlay. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + FGameplayTagContainer InitialTags; + + /** Current set of gameplay tags of the actor. */ + UPROPERTY(ReplicatedUsing = ReceivedCurrentTags) + FGameplayTagContainer CurrentTags; + + /** Event when the current gameplay tags of the actor have changed. */ + virtual void NotifyOnCurrentTagsChanged(); + + UFUNCTION() + void ReceivedCurrentTags(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSLog.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSLog.h new file mode 100644 index 00000000..b53081bf --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSLog.h @@ -0,0 +1,5 @@ +#pragma once + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogRTS, Log, All); diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSNameComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSNameComponent.h new file mode 100644 index 00000000..409a8356 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSNameComponent.h @@ -0,0 +1,27 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSNameComponent.generated.h" + + +/** +* Adds a localizable name to the actor. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSNameComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + /** Gets the name of the actor. */ + UFUNCTION(BlueprintPure) + FText GetName() const; + +private: + /** Name of the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + FText Name; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSOrderType.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSOrderType.h new file mode 100644 index 00000000..a11bc262 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSOrderType.h @@ -0,0 +1,31 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "RTSOrderType.generated.h" + + +UENUM(BlueprintType) +enum class ERTSOrderType : uint8 +{ + /** Idle. */ + ORDER_None, + + /** Move to a destination in the world. */ + ORDER_Move, + + /** Attack another actor. */ + ORDER_Attack, + + /** Create a new construction site. */ + ORDER_BeginConstruction, + + /** Finish a building construction. */ + ORDER_ContinueConstruction, + + /** Gather resources. */ + ORDER_Gather, + + /** Return carried resources. */ + ORDER_ReturnResources +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSOwnerComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSOwnerComponent.h new file mode 100644 index 00000000..0bd6584d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSOwnerComponent.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSOwnerComponent.generated.h" + + +class AController; + +class ARTSPlayerState; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSOwnerComponentOwnerChangedSignature, AActor*, Actor, AController*, NewOwner); + + +/** +* Specifies the owning player of the actor. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSOwnerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSOwnerComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + + /** Gets the player owning the actor. */ + UFUNCTION(BlueprintPure) + ARTSPlayerState* GetPlayerOwner() const; + + /** Sets the player owning the actor. */ + UFUNCTION(BlueprintCallable) + void SetPlayerOwner(AController* Controller); + + + /** Checks whether the actor belongs to the same team as the specified one. */ + UFUNCTION(BlueprintPure) + bool IsSameTeamAsActor(AActor* Other) const; + + /** Checks whether the player owning the actor belongs to the same team as the specified player. */ + UFUNCTION(BlueprintPure) + bool IsSameTeamAsController(AController* C) const; + + /** Gets the index of the player that should initially own the actor. */ + uint8 GetInitialOwnerPlayerIndex(); + + + /** Event when the actor is owned by a different player. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSOwnerComponentOwnerChangedSignature OnOwnerChanged; + + +private: + /** Index of the player that should initially own the actor. */ + UPROPERTY(EditInstanceOnly, Category = "RTS") + uint8 InitialOwnerPlayerIndex; + + /** Player owning this actor. */ + UPROPERTY(Replicated) + ARTSPlayerState* PlayerOwner; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPawnAIController.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPawnAIController.h new file mode 100644 index 00000000..35e4b199 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPawnAIController.h @@ -0,0 +1,109 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "AIController.h" +#include "Templates/SubclassOf.h" + +#include "RTSOrderType.h" + +#include "RTSPawnAIController.generated.h" + + +class URTSAttackComponent; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRTSPawnAIControllerOrderChangedSignature, AActor*, Actor, ERTSOrderType, NewOrder); + + +/** +* AI controller that drives RTS unit movement and orders. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSPawnAIController : public AAIController +{ + GENERATED_BODY() + +public: + /** Makes the pawn look for a feasible target in its acquisition radius. */ + UFUNCTION(BlueprintCallable) + void FindTargetInAcquisitionRadius(); + + /** Checks whether the pawn has an order of the specified type. */ + UFUNCTION(BlueprintPure) + bool HasOrder(ERTSOrderType OrderType) const; + + /** Checks whether the pawn is idle, or has any orders. */ + UFUNCTION(BlueprintPure) + bool IsIdle() const; + + /** Makes the pawn attack the specified target. */ + UFUNCTION(BlueprintCallable) + void IssueAttackOrder(AActor* Target); + + /** Makes the pawn construct the specified building at the passed location. */ + UFUNCTION(BlueprintCallable) + void IssueBeginConstructionOrder(TSubclassOf BuildingClass, const FVector& TargetLocation); + + /** Makes the pawn continue constructing the specified building. */ + UFUNCTION(BlueprintCallable) + void IssueContinueConstructionOrder(AActor* ConstructionSite); + + /** Makes the pawn gather resources from the specified source. */ + UFUNCTION(BlueprintCallable) + void IssueGatherOrder(AActor* ResourceSource); + + /** Makes the pawn move towards the specified location. */ + UFUNCTION(BlueprintCallable) + void IssueMoveOrder(const FVector& Location); + + /** Makes the pawn move towards the closest resource drain and return all carried resources. */ + UFUNCTION(BlueprintCallable) + void IssueReturnResourcesOrder(); + + /** Makes the pawn stop all actions immediately. */ + UFUNCTION(BlueprintCallable) + void IssueStopOrder(); + + + /** Event when the pawn has received a new order. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSPawnAIControllerOrderChangedSignature OnOrderChanged; + + +protected: + virtual void OnPossess(APawn* InPawn) override; + +private: + /** Behavior tree to use for driving the pawn AI. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + UBehaviorTree* PawnBehaviorTreeAsset; + + /** Blackboard to use for holding all data relevant to the pawn AI. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + UBlackboardData* PawnBlackboardAsset; + + URTSAttackComponent* AttackComponent; + + void ApplyOrders(); + + void ClearBuildingClass(); + void ClearHomeLocation(); + void ClearTargetActor(); + void ClearTargetLocation(); + + void SetBuildingClass(int32 BuildingIndex); + void SetHomeLocation(const FVector& HomeLocation); + void SetOrderType(const ERTSOrderType OrderType); + void SetTargetActor(AActor* TargetActor); + void SetTargetLocation(const FVector& TargetLocation); + + bool TraceSphere( + const FVector& Location, + const float Radius, + AActor* ActorToIgnore, + ECollisionChannel TraceChannel, + TArray& OutHitResults); + + bool VerifyBlackboard(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPawnMovementComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPawnMovementComponent.h new file mode 100644 index 00000000..92daa50f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPawnMovementComponent.h @@ -0,0 +1,25 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/FloatingPawnMovement.h" + +#include "RTSPawnMovementComponent.generated.h" + + +/** Simple pawn movement that also updates rotation. */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSPawnMovementComponent : public UFloatingPawnMovement +{ + GENERATED_BODY() + +public: + URTSPawnMovementComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + virtual void UpdateComponentVelocity() override; + +private: + /** Whether to automatically update the pawn rotation to match its velocity. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + bool bUpdateRotation; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerAIController.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerAIController.h new file mode 100644 index 00000000..d4315665 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerAIController.h @@ -0,0 +1,84 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "AIController.h" +#include "Templates/SubclassOf.h" + +#include "Economy/RTSResourceType.h" + +#include "RTSPlayerAIController.generated.h" + + +class UBehaviorTree; +class UBlackboardData; +class APawn; + +class URTSPlayerResourcesComponent; + + +/** +* AI controller that drives strategic RTS player AI. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSPlayerAIController : public AAIController +{ + GENERATED_BODY() + +public: + ARTSPlayerAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + /** Checks the build order and returns the class of the next pawn to produce, or the Pawn class if nothing needs to be produced. */ + UFUNCTION(BlueprintPure) + TSubclassOf GetNextPawnToProduce() const; + + /** Gets the primary resource drain of the AI. */ + UFUNCTION(BlueprintPure) + virtual AActor* GetPrimaryResourceDrain() const; + + /** Gets the closest resource source of the primary resource type around the first resource drain of the AI. */ + UFUNCTION(BlueprintPure) + virtual AActor* GetPrimaryResourceSource() const; + + /** Checks the available resources for the AI and whether it can pay for a pawn of the specified class. */ + UFUNCTION(BlueprintPure) + bool CanPayFor(TSubclassOf PawnClass) const; + + /** Checks whether the AI meets all tech tree requirements for a pawn of the specified class. */ + UFUNCTION(BlueprintPure) + bool MeetsAllRequirementsFor(TSubclassOf PawnClass) const; + + /** Selects an arbitrary production actor for producing a pawn of the specified class and starts production. */ + UFUNCTION(BlueprintCallable) + bool StartProduction(TSubclassOf PawnClass); + +protected: + virtual void OnPossess(APawn* InPawn) override; + + +private: + /** Behavior tree to use for driving the player AI. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + UBehaviorTree* PlayerBehaviorTreeAsset; + + /** Blackboard to use for holding all data relevant to the player AI. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + UBlackboardData* PlayerBlackboardAsset; + + /** Units and buildings the AI should produce, in order. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> BuildOrder; + + /** Maximum distance of a new building to an existing one, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + float MaximumBaseBuildingDistance; + + /** Type of the primary resource for the AI to gather (e.g. used for placing resource drains). */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TSubclassOf PrimaryResourceType; + + /** Stores the resources available for this player. */ + UPROPERTY(VisibleAnywhere, Category = "RTS") + URTSPlayerResourcesComponent* PlayerResourcesComponent; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerAdvantageComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerAdvantageComponent.h new file mode 100644 index 00000000..ddff4a97 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerAdvantageComponent.h @@ -0,0 +1,41 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSPlayerAdvantageComponent.generated.h" + + +/** +* Attach to player or AI controllers. Provides bonuses for various gameplay elements. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSPlayerAdvantageComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSPlayerAdvantageComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Whether units controlled by the player are invulnerable, or not. */ + bool IsGodModeEnabled() const; + + /** Gets the factor to multiply all construction and production speed with. */ + float GetSpeedBoostFactor() const; + + /** Sets whether units controlled by the player are invulnerable, or not. */ + void SetGodModeEnabled(bool bInGodModeEnabled); + + /** Sets the factor to multiply all construction and production speed with. */ + void SetSpeedBoostFactor(float InSpeedBoostFactor); + +private: + /** Whether units controlled by the player are invulnerable, or not. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Advantage") + bool bGodModeEnabled; + + /** Factor to multiply all construction and production speed with. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Advantage") + float SpeedBoostFactor; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerController.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerController.h new file mode 100644 index 00000000..7f16db40 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerController.h @@ -0,0 +1,529 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/PlayerController.h" +#include "Templates/SubclassOf.h" + +#include "RTSControlGroup.h" + +#include "RTSPlayerController.generated.h" + + +class USkeletalMesh; + +class ARTSBuildingCursor; +class ARTSCameraBoundsVolume; +class URTSPlayerAdvantageComponent; +class URTSPlayerResourcesComponent; +class ARTSPlayerState; +class URTSResourceType; +class ARTSTeamInfo; +class ARTSVisionInfo; + + +/** + * Player controller with RTS features, such as selection and mouse camera movement. + */ +UCLASS() +class REALTIMESTRATEGY_API ARTSPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + ARTSPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + virtual void PlayerTick(float DeltaTime) override; + + + /** Gets the actor currently hovered by this player. */ + UFUNCTION(BlueprintPure) + AActor* GetHoveredActor() const; + + /** Gets the replicated state of this player. */ + UFUNCTION(BlueprintPure) + ARTSPlayerState* GetPlayerState() const; + + /** Gets the list of units currently selected by this player. */ + UFUNCTION(BlueprintPure) + TArray GetSelectedActors() const; + + /** Casts a ray from the specified screen position and collects the results. */ + bool GetObjectsAtScreenPosition(FVector2D ScreenPosition, TArray& OutHitResults) const; + + /** Casts a ray to find any objects at the specified world position. */ + bool GetObjectsAtWorldPosition(const FVector& WorldPositionXY, TArray& OutHitResults) const; + + /** Gets the current selection frame, in screen space. */ + bool GetSelectionFrame(FIntRect& OutSelectionFrame) const; + + /** Gets the team this player belongs to. */ + UFUNCTION(BlueprintPure) + ARTSTeamInfo* GetTeamInfo() const; + + /** Orders all selected units to attack the specified unit. */ + UFUNCTION(BlueprintCallable) + bool IssueAttackOrder(AActor* Target); + + /** Orders a selected builder to construct the specified building at the passed location. */ + UFUNCTION(BlueprintCallable) + bool IssueBeginConstructionOrder(TSubclassOf BuildingClass, const FVector& TargetLocation); + + /** Orders selected builders to finish constructing the specified building. */ + UFUNCTION(BlueprintCallable) + bool IssueContinueConstructionOrder(AActor* ConstructionSite); + + /** Orders selected gatherers to gather resources from the specified source. */ + UFUNCTION(BlueprintCallable) + bool IssueGatherOrder(AActor* ResourceSource); + + /** Orders all selected units to move to the specified location. */ + UFUNCTION(BlueprintCallable) + bool IssueMoveOrder(const FVector& TargetLocation); + + /** Gets a selected actor suitable for production. */ + AActor* GetSelectedProductionActorFor(TSubclassOf ProductClass) const; + + /** Checks whether this player can begin producing the product with the specified index (e.g. can pay for it), and shows an error message otherwise. */ + UFUNCTION(BlueprintPure) + bool CheckCanIssueProductionOrder(TSubclassOf ProductClass); + + /** Orders the selected production actor to start producing the product with the specified index. */ + UFUNCTION(BlueprintCallable) + void IssueProductionOrder(TSubclassOf ProductClass); + + /** Orders all selected units to stop all current actions. */ + UFUNCTION(BlueprintCallable) + void IssueStopOrder(); + + /** Selects the specified actors. */ + UFUNCTION(BlueprintCallable) + void SelectActors(TArray Actors); + + /** Saves the current selection to the specified control group. */ + UFUNCTION(BlueprintCallable) + void SaveControlGroup(int32 Index); + + UFUNCTION(BlueprintCallable) void SaveControlGroup0(); + UFUNCTION(BlueprintCallable) void SaveControlGroup1(); + UFUNCTION(BlueprintCallable) void SaveControlGroup2(); + UFUNCTION(BlueprintCallable) void SaveControlGroup3(); + UFUNCTION(BlueprintCallable) void SaveControlGroup4(); + UFUNCTION(BlueprintCallable) void SaveControlGroup5(); + UFUNCTION(BlueprintCallable) void SaveControlGroup6(); + UFUNCTION(BlueprintCallable) void SaveControlGroup7(); + UFUNCTION(BlueprintCallable) void SaveControlGroup8(); + UFUNCTION(BlueprintCallable) void SaveControlGroup9(); + + /** Restores the selection saved in the specified control group. */ + UFUNCTION(BlueprintCallable) + void LoadControlGroup(int32 Index); + + UFUNCTION(BlueprintCallable) void LoadControlGroup0(); + UFUNCTION(BlueprintCallable) void LoadControlGroup1(); + UFUNCTION(BlueprintCallable) void LoadControlGroup2(); + UFUNCTION(BlueprintCallable) void LoadControlGroup3(); + UFUNCTION(BlueprintCallable) void LoadControlGroup4(); + UFUNCTION(BlueprintCallable) void LoadControlGroup5(); + UFUNCTION(BlueprintCallable) void LoadControlGroup6(); + UFUNCTION(BlueprintCallable) void LoadControlGroup7(); + UFUNCTION(BlueprintCallable) void LoadControlGroup8(); + UFUNCTION(BlueprintCallable) void LoadControlGroup9(); + + /** Whether the hotkey for showing all construction progress bars is currently pressed, or not. */ + UFUNCTION(BlueprintPure) + bool IsConstructionProgressBarHotkeyPressed() const; + + /** Whether the hotkey for showing all health bars is currently pressed, or not. */ + UFUNCTION(BlueprintPure) + bool IsHealthBarHotkeyPressed() const; + + /** Whether the hotkey for showing all production progress bars is currently pressed, or not. */ + UFUNCTION(BlueprintPure) + bool IsProductionProgressBarHotkeyPressed() const; + + /** Checks whether this player can begin placing a building of the specified class (e.g. can pay for it), and shows an error message otherwise. */ + UFUNCTION(BlueprintPure) + bool CheckCanBeginBuildingPlacement(TSubclassOf BuildingClass); + + /** Begin finding a suitable location for constructing a building of the specified type. */ + UFUNCTION(BlueprintCallable) + void BeginBuildingPlacement(TSubclassOf BuildingClass); + + /** + * Checks whether the specified building can be placed at the passed location. + * Default implementation checks for any dynamic objects within a box of the specified collision size. + * You may add custom building placement logic here, e.g. requires other nearby building, cursed terrain, energy field, fixed slot. + */ + UFUNCTION(BlueprintNativeEvent, Category = "RTS", meta = (DisplayName = "CanPlaceBuilding")) + bool CanPlaceBuilding(TSubclassOf BuildingClass, const FVector& Location) const; + virtual bool CanPlaceBuilding_Implementation(TSubclassOf BuildingClass, const FVector& Location) const; + + /** Surrenders the current game. */ + UFUNCTION(BlueprintCallable) + void Surrender(); + + virtual void GameHasEnded(class AActor* EndGameFocus = NULL, bool bIsWinner = false) override; + + /** Notifies this client that the game has ended. */ + UFUNCTION(Reliable, Client) + virtual void ClientGameHasEnded(bool bIsWinner); + + + /** Event when this player is now owning the specified actor. */ + virtual void NotifyOnActorOwnerChanged(AActor* Actor); + + /** Event when the player begins placing a building. */ + virtual void NotifyOnBuildingPlacementStarted(TSubclassOf BuildingClass); + + /** Event when the player confirms a location for placing a building. */ + virtual void NotifyOnBuildingPlacementConfirmed(TSubclassOf BuildingClass, const FVector& Location); + + /** Event when the player receives an error placing a building at a specific location. */ + virtual void NotifyOnBuildingPlacementError(TSubclassOf BuildingClass, const FVector& Location); + + /** Event when the player cancels placing a building. */ + virtual void NotifyOnBuildingPlacementCancelled(TSubclassOf BuildingClass); + + /** Event when an error has occurred that can be presented to the user. */ + virtual void NotifyOnErrorOccurred(const FString& ErrorMessage); + + /** Event when the game has ended. */ + virtual void NotifyOnGameHasEnded(bool bIsWinner); + + /** Event when an actor has received an attack order. */ + virtual void NotifyOnIssuedAttackOrder(APawn* OrderedPawn, AActor* Target); + + /** Event when an actor has received a begin construction order. */ + virtual void NotifyOnIssuedBeginConstructionOrder(APawn* OrderedPawn, TSubclassOf BuildingClass, const FVector& TargetLocation); + + /** Event when an actor has received a continue construction order. */ + virtual void NotifyOnIssuedContinueConstructionOrder(APawn* OrderedPawn, AActor* ConstructionSite); + + /** Event when an actor has received a gather order. */ + virtual void NotifyOnIssuedGatherOrder(APawn* OrderedPawn, AActor* ResourceSource); + + /** Event when an actor has received a move order. */ + virtual void NotifyOnIssuedMoveOrder(APawn* OrderedPawn, const FVector& TargetLocation); + + /** Event when an actor has received a production order. */ + virtual void NotifyOnIssuedProductionOrder(AActor* OrderedActor, TSubclassOf ProductClass); + + /** Event when an actor has received a stop order. */ + virtual void NotifyOnIssuedStopOrder(APawn* OrderedPawn); + + /** Event when the player has clicked a spot on the minimap. */ + virtual void NotifyOnMinimapClicked(const FPointerEvent& InMouseEvent, const FVector2D& MinimapPosition, const FVector& WorldPosition); + + /** Event when the set of selected actors of this player has changed. */ + virtual void NotifyOnSelectionChanged(const TArray& Selection); + + /** Event when the team of this player has changed. */ + virtual void NotifyOnTeamChanged(ARTSTeamInfo* NewTeam); + + /** Event when vision info for this player has been replicated and is available. */ + virtual void NotifyOnVisionInfoAvailable(ARTSVisionInfo* VisionInfo); + + /** Event when this player is now owning the specified actor. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Ownership", meta = (DisplayName = "OnActorOwnerChanged")) + void ReceiveOnActorOwnerChanged(AActor* Actor); + + /** Event when the player begins placing a building. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Construction", meta = (DisplayName = "OnBuildingPlacementStarted")) + void ReceiveOnBuildingPlacementStarted(TSubclassOf BuildingClass); + + /** Event when the player confirms a location for placing a building. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Construction", meta = (DisplayName = "OnBuildingPlacementConfirmed")) + void ReceiveOnBuildingPlacementConfirmed(TSubclassOf BuildingClass, const FVector& Location); + + /** Event when the player receives an error placing a building at a specific location. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Construction", meta = (DisplayName = "OnBuildingPlacementError")) + void ReceiveOnBuildingPlacementError(TSubclassOf BuildingClass, const FVector& Location); + + /** Event when the player cancels placing a building. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Construction", meta = (DisplayName = "OnBuildingPlacementCancelled")) + void ReceiveOnBuildingPlacementCancelled(TSubclassOf BuildingClass); + + /** Event when an error has occurred that can be presented to the user. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Error", meta = (DisplayName = "OnErrorOccurred")) + void ReceiveOnErrorOccurred(const FString& ErrorMessage); + + /** Event when the game has ended. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Game", meta = (DisplayName = "OnGameHasEnded")) + void ReceiveOnGameHasEnded(bool bIsWinner); + + /** Event when an actor has received an attack order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedAttackOrder")) + void ReceiveOnIssuedAttackOrder(APawn* OrderedPawn, AActor* Target); + + /** Event when an actor has received a begin construction order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedBeginConstructionOrder")) + void ReceiveOnIssuedBeginConstructionOrder(APawn* OrderedPawn, TSubclassOf BuildingClass, const FVector& TargetLocation); + + /** Event when an actor has received a continue construction order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedContinueConstructionOrder")) + void ReceiveOnIssuedContinueConstructionOrder(APawn* OrderedPawn, AActor* ConstructionSite); + + /** Event when an actor has received a gather order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedGatherOrder")) + void ReceiveOnIssuedGatherOrder(APawn* OrderedPawn, AActor* ResourceSource); + + /** Event when an actor has received a move order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedMoveOrder")) + void ReceiveOnIssuedMoveOrder(APawn* OrderedPawn, const FVector& TargetLocation); + + /** Event when an actor has received a production order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedProductionOrder")) + void ReceiveOnIssuedProductionOrder(AActor* OrderedActor, TSubclassOf ProductClass); + + /** Event when an actor has received a stop order. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Orders", meta = (DisplayName = "OnIssuedStopOrder")) + void ReceiveOnIssuedStopOrder(APawn* OrderedPawn); + + /** Event when the player has clicked a spot on the minimap. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Minimap", meta = (DisplayName = "OnMinimapClicked")) + void ReceiveOnMinimapClicked(const FPointerEvent& InMouseEvent, const FVector2D& MinimapPosition, const FVector& WorldPosition); + + /** Event when the set of selected actors of this player has changed. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Selection", meta = (DisplayName = "OnSelectionChanged")) + void ReceiveOnSelectionChanged(const TArray& Selection); + + /** Event when the team of this player has changed. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Team", meta = (DisplayName = "OnTeamChanged")) + void ReceiveOnTeamChanged(ARTSTeamInfo* NewTeam); + + /** Event when vision info for this player has been replicated and is available. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Vision", meta = (DisplayName = "OnVisionInfoAvailable")) + void ReceiveOnVisionInfoAvailable(ARTSVisionInfo* VisionInfo); + + +protected: + virtual void BeginPlay() override; + virtual void SetupInputComponent() override; + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +private: + /** Movement speed of the camera when moved with keys or mouse, in cm/sec. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Camera", meta = (ClampMin = 0)) + float CameraSpeed; + + /** How fast to zoom the camera in and out, in cm/sec. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Camera", meta = (ClampMin = 0)) + float CameraZoomSpeed; + + /** Maximum distance of the camera from the player pawn, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Camera", meta = (ClampMin = 0)) + float MinCameraDistance; + + /** Minimum distance of the camera from the player pawn, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Camera", meta = (ClampMin = 0)) + float MaxCameraDistance; + + /** Distance from the screen border at which the mouse cursor causes the camera to move, in pixels. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Camera", meta = (ClampMin = 0)) + int32 CameraScrollThreshold; + + /** Preview to use for placing buildings. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Construction") + TSubclassOf BuildingCursorClass; + + /** Provides bonuses for various gameplay elements for this player. */ + UPROPERTY(VisibleAnywhere, Category = "RTS") + URTSPlayerAdvantageComponent* PlayerAdvantageComponent; + + /** Stores the resources available for this player. */ + UPROPERTY(VisibleAnywhere, Category = "RTS") + URTSPlayerResourcesComponent* PlayerResourcesComponent; + + /** Volume that restricts the camera movement of this player. */ + UPROPERTY() + ARTSCameraBoundsVolume* CameraBoundsVolume; + + /** Last horizontal axis input applied to camera movement. */ + float CameraLeftRightAxisValue; + + /** Last vertical axis input applied to camera movement. */ + float CameraUpDownAxisValue; + + /** Last zoom axis input applied to camera movement. */ + float CameraZoomAxisValue; + + /** Saved selections of this player. */ + UPROPERTY() + TArray ControlGroups; + + /** Actor currently hovered by this player. */ + UPROPERTY() + AActor* HoveredActor; + + /** World position currently hovered by this player. */ + FVector HoveredWorldPosition; + + /** Actors selected by this player. */ + UPROPERTY() + TArray SelectedActors; + + /** Type of the building currently being placed, if any. */ + TSubclassOf BuildingBeingPlacedClass; + + /** Current cursor for placing a new building. */ + UPROPERTY() + ARTSBuildingCursor* BuildingCursor; + + /** Whether we're currently creating a selection frame by dragging the mouse. */ + bool bCreatingSelectionFrame; + + /** Mouse position on screen when creating the selection frame started. */ + FVector2D SelectionFrameMouseStartPosition; + + /** Whether the hotkey for showing all construction progress bars is currently pressed, or not. */ + bool bConstructionProgressBarHotkeyPressed; + + /** Whether the hotkey for showing all health bars is currently pressed, or not. */ + bool bHealthBarHotkeyPressed; + + /** Whether the hotkey for showing all production progress bars is currently pressed, or not. */ + bool bProductionProgressBarHotkeyPressed; + + /** Whether to add clicked units to the current selection. */ + bool bAddSelectionHotkeyPressed; + + /** Whether to add clicked units to the current selection, if they're not already selected, and deselect them otherwise. */ + bool bToggleSelectionHotkeyPressed; + + /** Time to wait before playing the next selection sound, in seconds. */ + float SelectionSoundCooldownRemaining; + + + /** Casts a ray from the current mouse position and collects the results. */ + bool GetObjectsAtPointerPosition(TArray& OutHitResults) const; + + /** Casts a box from the current selection frame and collects the results. */ + bool GetObjectsInSelectionFrame(TArray& OutHitResults) const; + + /** Traces all relevant objects using the specified ray. */ + bool TraceObjects(const FVector& WorldOrigin, const FVector& WorldDirection, TArray& OutHitResults) const; + + /** Checks whether the specified actor is valid and selectable. */ + bool IsSelectableActor(AActor* Actor) const; + + /** Automatically issues the most reasonable order for the current pointer position. */ + UFUNCTION() + void IssueOrder(); + + /** Automatically issues the most reasonable order for the specified targets. */ + void IssueOrderTargetingObjects(TArray& HitResults); + + /** Cancels constructing the specified actor, destroying the construction site. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerCancelConstruction(AActor* ConstructionSite); + + /** Orders the passed unit to attack the specified unit. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerIssueAttackOrder(APawn* OrderedPawn, AActor* Target); + + /** Orders a selected builder to construct the specified building at the passed location. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerIssueBeginConstructionOrder(APawn* OrderedPawn, TSubclassOf BuildingClass, const FVector& TargetLocation); + + /** Orders selected gatherers to gather resources from the specified source. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerIssueGatherOrder(APawn* OrderedPawn, AActor* ResourceSource); + + /** Orders selected builders to finish constructing the specified building. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerIssueContinueConstructionOrder(APawn* OrderedPawn, AActor* ConstructionSite); + + /** Orders the passed unit to move to the specified location. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerIssueMoveOrder(APawn* OrderedPawn, const FVector& TargetLocation); + + /** Orders the passed unit to stop all current actions. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerIssueStopOrder(APawn* OrderedPawn); + + /** Start producing the specified product at the specified actor. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerStartProduction(AActor* ProductionActor, TSubclassOf ProductClass); + + /** Cancels the current production at the specified actor. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerCancelProduction(AActor* ProductionActor); + + /** Surrenders the current match. */ + UFUNCTION(Reliable, Server, WithValidation) + void ServerSurrender(); + + /** Applies horizontal axis input to camera movement. */ + void MoveCameraLeftRight(float Value); + + /** Applies vertical axis input to camera movement. */ + void MoveCameraUpDown(float Value); + + /** Applies zoom input to camera movement. */ + void ZoomCamera(float Value); + + /** Remembers the current mouse position for multi-selection, finished by FinishSelectActors. */ + UFUNCTION() + void StartSelectActors(); + + /** Selects all selectable actors within the created selection frame, started by StartSelectActors. */ + UFUNCTION() + void FinishSelectActors(); + + /** Force showing all construction progress bars. */ + UFUNCTION() + void StartShowingConstructionProgressBars(); + + /** Stop showing all construction progress bars. */ + UFUNCTION() + void StopShowingConstructionProgressBars(); + + /** Force showing all health bars. */ + UFUNCTION() + void StartShowingHealthBars(); + + /** Stop showing all health bars. */ + UFUNCTION() + void StopShowingHealthBars(); + + /** Force showing all production progress bars. */ + UFUNCTION() + void StartShowingProductionProgressBars(); + + /** Stop showing all production progress bars. */ + UFUNCTION() + void StopShowingProductionProgressBars(); + + /** Start adding clicked units to the current selection. */ + UFUNCTION() + void StartAddSelection(); + + /** Stop adding clicked units to the current selection. */ + UFUNCTION() + void StopAddSelection(); + + /** Start adding clicked units to the current selection, if they're not already selected, and deselecting them otherwise. */ + UFUNCTION() + void StartToggleSelection(); + + /** Stop adding clicked units to the current selection, if they're not already selected, and deselecting them otherwise. */ + UFUNCTION() + void StopToggleSelection(); + + /** Confirms placing the current building at the hovered location. */ + UFUNCTION() + void ConfirmBuildingPlacement(); + + /** Cancels placing the current building without effect. */ + UFUNCTION() + void CancelBuildingPlacement(); + + /** Cancels the construction of the first selected building. */ + UFUNCTION() + void CancelConstruction(); + + /** Cancels the current production of the first selected building. */ + UFUNCTION() + void CancelProduction(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerStart.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerStart.h new file mode 100644 index 00000000..e2077525 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerStart.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/PlayerStart.h" + +#include "RTSPlayerStart.generated.h" + + +/** +* Player start with RTS features, such as which player has been spawned at this start. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSPlayerStart : public APlayerStart +{ + GENERATED_BODY() + +public: + /** Gets the team to add the spawned player to. */ + UFUNCTION(BlueprintPure) + int32 GetTeamIndex() const; + + /** Gets the player who has been spawned at this start. */ + UFUNCTION(BlueprintPure) + AController* GetPlayer() const; + + /** Sets the player who's been spawned at this start. */ + void SetPlayer(AController* InPlayer); + +private: + /** Team to add the spawned player to. */ + UPROPERTY(EditInstanceOnly, Category = "Team") + uint8 TeamIndex; + + /** Player who's been spawned at this start. */ + UPROPERTY() + AController* Player; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerState.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerState.h new file mode 100644 index 00000000..06edf903 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPlayerState.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/PlayerState.h" + +#include "RTSPlayerState.generated.h" + + +class ARTSTeamInfo; + + +/** +* Player state with RTS features, such as teams. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSPlayerState : public APlayerState +{ + GENERATED_BODY() + +public: + static const uint8 PLAYER_INDEX_NONE; + + ARTSPlayerState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + /** Gets the index of the player. */ + UFUNCTION(BlueprintPure) + uint8 GetPlayerIndex() const; + + /** Gets the team this player belongs to. */ + UFUNCTION(BlueprintPure) + ARTSTeamInfo* GetTeam() const; + + /** Sets the index of the player. */ + void SetPlayerIndex(uint8 InPlayerIndex); + + /** Sets the team this player belongs to. */ + void SetTeam(ARTSTeamInfo* InTeam); + + /** Checks whether this player belong to the same team as the specified one. */ + UFUNCTION(BlueprintPure) + bool IsSameTeamAs(ARTSPlayerState* Other) const; + + + /** Event when this player changed team. */ + virtual void NotifyOnTeamChanged(ARTSTeamInfo* NewTeam); + + /** Event when this player changed team. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS|Team", meta = (DisplayName = "OnTeamChanged")) + void ReceiveOnTeamChanged(ARTSTeamInfo* NewTeam); + +private: + /** Index of the player. */ + UPROPERTY(Replicated) + uint8 PlayerIndex; + + /** Team this player belongs to. */ + UPROPERTY(ReplicatedUsing = OnTeamChanged) + ARTSTeamInfo* Team; + + + UFUNCTION() + void OnTeamChanged(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPortraitComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPortraitComponent.h new file mode 100644 index 00000000..06472bec --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSPortraitComponent.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSPortraitComponent.generated.h" + + +class UTexture2D; + + +/** +* Adds a portrait to the actor that can be shown in the UI. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSPortraitComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + /** Gets the portrait of the actor. Can be shown in the UI. */ + UFUNCTION(BlueprintPure) + UTexture2D* GetPortrait() const; + +private: + /** Portrait of the actor. Can be shown in the UI. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + UTexture2D* Portrait; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSRequirementsComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSRequirementsComponent.h new file mode 100644 index 00000000..6308a9b3 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSRequirementsComponent.h @@ -0,0 +1,29 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" +#include "Templates/SubclassOf.h" + +#include "RTSRequirementsComponent.generated.h" + + +class AActor; + + +/** Adds tech tree requirements to the actor. */ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSRequirementsComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + /** Gets the types of actors the player needs to own in order to create this actor. */ + UFUNCTION(BlueprintPure) + TArray> GetRequiredActors() const; + +private: + /** Types of actors the player needs to own in order to create this actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + TArray> RequiredActors; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSSelectableComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSSelectableComponent.h new file mode 100644 index 00000000..1b2185fa --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSSelectableComponent.h @@ -0,0 +1,74 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSSelectableComponent.generated.h" + + +class UMaterialInterface; +class UMaterialInstanceDynamic; +class USoundCue; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRTSSelectableComponentSelectedSignature, AActor*, Actor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRTSSelectableComponentDeselectedSignature, AActor*, Actor); + + +/** + * Allows selecting the actor, e.g. by left-click. + */ +UCLASS(meta=(BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSSelectableComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + virtual void BeginPlay() override; + + + /** Selects the unit for the local player. */ + UFUNCTION(BlueprintCallable) + void SelectActor(); + + /** Deselects the unit for the local player. */ + UFUNCTION(BlueprintCallable) + void DeselectActor(); + + /** Checks whether the unit is currently selected by the local player, or not. */ + UFUNCTION(BlueprintPure) + bool IsSelected() const; + + /** Gets the sound to play when the actor is selected. */ + USoundCue* GetSelectedSound() const; + + /** Event when the actor has been deselected. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSSelectableComponentDeselectedSignature OnDeselected; + + /** Event when the actor has been selected. */ + UPROPERTY(BlueprintAssignable, Category = "RTS") + FRTSSelectableComponentSelectedSignature OnSelected; + + +private: + /** Material for rendering the selection circle of the actor. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + UMaterialInterface* SelectionCircleMaterial; + + /** Sound to play when the actor is selected. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS") + USoundCue* SelectedSound; + + /** Whether the unit is currently selected by the local player, or not. */ + bool bSelected; + + /** Decal used for rendering the selection circle of the actor. */ + UPROPERTY() + UDecalComponent* DecalComponent; + + /** Material instance for rendering the selection circle of the actor. */ + UPROPERTY() + UMaterialInstanceDynamic* SelectionCircleMaterialInstance; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSTeamInfo.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSTeamInfo.h new file mode 100644 index 00000000..1ebd285f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/RTSTeamInfo.h @@ -0,0 +1,51 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Info.h" + +#include "RTSTeamInfo.generated.h" + + +/** +* Team that consists of multiple players. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSTeamInfo : public AInfo +{ + GENERATED_BODY() + +public: + ARTSTeamInfo(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + /** Adds the specified player to this team. */ + UFUNCTION(BlueprintCallable, Category = "Team") + virtual void AddToTeam(AController* Player); + + /** Removes the specified player from this team. */ + UFUNCTION(BlueprintCallable, Category = "Team") + virtual void RemoveFromTeam(AController* Player); + + /** Gets the index of this team. */ + UFUNCTION(BlueprintPure) + uint8 GetTeamIndex() const; + + /** Gets all players belonging to this team. */ + UFUNCTION(BlueprintPure) + TArray GetTeamMembers() const; + + /** Sets the index of this team. */ + void SetTeamIndex(uint8 InTeamIndex); + + +private: + /** Index of this team. */ + UPROPERTY(Replicated) + uint8 TeamIndex; + + /** Players on this team. */ + UPROPERTY() + TArray TeamMembers; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSActorWidgetComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSActorWidgetComponent.h new file mode 100644 index 00000000..df1169cb --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSActorWidgetComponent.h @@ -0,0 +1,24 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/WidgetComponent.h" + +#include "RTSActorWidgetComponent.generated.h" + + +/** +* Adds a customizable UI widget to the actor. +*/ +UCLASS() +class REALTIMESTRATEGY_API URTSActorWidgetComponent : public UWidgetComponent +{ + GENERATED_BODY() + +public: + URTSActorWidgetComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Event when the size of the actor on screen been calculated from its collision size. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void UpdatePositionAndSize(const FVector2D& ActorScreenSize); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSFloatingCombatTextComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSFloatingCombatTextComponent.h new file mode 100644 index 00000000..14f3f7d3 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSFloatingCombatTextComponent.h @@ -0,0 +1,37 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "UI/RTSFloatingCombatTextData.h" + +#include "RTSFloatingCombatTextComponent.generated.h" + + +/** +* Stores floating texts to be displayed above the actor. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSFloatingCombatTextComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSFloatingCombatTextComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + + /** Adds the specified text to be displayed above the actor in the HUD for a short time. */ + UFUNCTION(BlueprintCallable) + void AddText(const FString& Text, const FLinearColor& Color, float Scale, float Lifetime); + + /** Gets the floating combat texts currently being displayed to the player. */ + TArray GetTexts() const; + +private: + /** Floating combat texts currently being displayed to the player. */ + TArray Texts; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSFloatingCombatTextData.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSFloatingCombatTextData.h new file mode 100644 index 00000000..58047e97 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSFloatingCombatTextData.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" + + +struct REALTIMESTRATEGY_API FRTSFloatingCombatTextData +{ + /** Text to display. */ + FString Text; + + /** Color to display the text with. */ + FLinearColor Color; + + /** Scale to display the text with. */ + float Scale; + + /** Total time to show the text on screen before removing it. */ + float Lifetime; + + /** Remaining time to show the text on screen before removing it. */ + float RemainingLifetime; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSHUD.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSHUD.h new file mode 100644 index 00000000..240db21a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSHUD.h @@ -0,0 +1,174 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/HUD.h" + +#include "RTSHUD.generated.h" + + +/** +* HUD with RTS features, such as showing a selection frame. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSHUD : public AHUD +{ + GENERATED_BODY() + +public: + virtual void DrawHUD() override; + + /** Event for drawaing a floating combat text. */ + virtual void NotifyDrawFloatingCombatText( + AActor* Actor, + const FString& Text, + const FLinearColor& Color, + float Scale, + float Lifetime, + float RemainingLifetime, + float LifetimePercentage, + float SuggestedTextLeft, + float SuggestedTextTop); + + /** Event for drawing the selection frame because the mouse is being dragged. */ + virtual void NotifyDrawSelectionFrame(float ScreenX, float ScreenY, float Width, float Height); + + /** Event for hiding the selection frame because the mouse isn't being dragged. */ + virtual void NotifyHideSelectionFrame(); + + /** Event for drawaing a floating combat text. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS", meta = (DisplayName = "DrawFloatingCombatText")) + void ReceiveDrawFloatingCombatText( + AActor* Actor, + const FString& Text, + const FLinearColor& Color, + float Scale, + float Lifetime, + float RemainingLifetime, + float LifetimePercentage, + float SuggestedTextLeft, + float SuggestedTextTop); + + /** Event for drawing the selection frame because the mouse is being dragged. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS", meta = (DisplayName = "DrawSelectionFrame")) + void ReceiveDrawSelectionFrame(float ScreenX, float ScreenY, float Width, float Height); + + /** Event for hiding the selection frame because the mouse isn't being dragged. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS", meta = (DisplayName = "HideSelectionFrame")) + void ReceiveHideSelectionFrame(); + + UFUNCTION(BlueprintPure) + FVector2D GetActorCenterOnScreen(AActor* Actor) const; + + UFUNCTION(BlueprintPure) + FVector2D GetActorSizeOnScreen(AActor* Actor) const; + +private: + /** Whether to always show all health bars. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Health Bars") + bool bAlwaysShowHealthBars; + + /** Whether to show health bars for hovered units. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Health Bars") + bool bShowHoverHealthBars = true; + + /** Whether to show health bars for selected units. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Health Bars") + bool bShowSelectionHealthBars = true; + + /** Whether to show health bars while the respective hotkey is pressed. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Health Bars") + bool bShowHotkeyHealthBars = true; + + + /** Whether to always show all construction progress bars. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Construction Progress Bars") + bool bAlwaysShowConstructionProgressBars; + + /** Whether to show construction progress bars for hovered units. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Construction Progress Bars") + bool bShowHoverConstructionProgressBars = true; + + /** Whether to show construction progress bars for selected units. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Construction Progress Bars") + bool bShowSelectionConstructionProgressBars = true; + + /** Whether to show construction progress bars while the respective hotkey is pressed. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Construction Progress Bars") + bool bShowHotkeyConstructionProgressBars = true; + + + /** Whether to always show all production progress bars. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Production Progress Bars") + bool bAlwaysShowProductionProgressBars; + + /** Whether to show production progress bars for hovered units. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Production Progress Bars") + bool bShowHoverProductionProgressBars = true; + + /** Whether to show production progress bars for selected units. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Production Progress Bars") + bool bShowSelectionProductionProgressBars = true; + + /** Whether to show production progress bars while the respective hotkey is pressed. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Production Progress Bars") + bool bShowHotkeyProductionProgressBars = true; + + + /** Whether to show floating combat texts above actors. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Floating Combat Texts") + bool bShowFloatingCombatTexts = true; + + /** How many pixels the floating combat text should rise, per second. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Floating Combat Texts") + float FloatingCombatTextSpeed = 20.0f; + + /** Whether to automatically adjust the alpha value of the color of floating combat texts depending on their elapsed lifetime. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS|Floating Combat Texts") + bool bFadeOutFloatingCombatTexts = true; + + + /** Whether we've been drawing the selection frame last frame, because the mouse was being dragged. */ + bool bWasDrawingSelectionFrame; + + /** Actor the player has hovering last frame. */ + UPROPERTY() + AActor* OldHoveredActor; + + + /** Draws the current selection frame if mouse is being dragged. */ + void DrawSelectionFrame(); + + /** Draws floating combat texts. */ + void DrawFloatingCombatTexts(); + + /** Draws unit health bars. */ + void DrawHealthBars(); + + /** Draws the health bar of the specified actor. */ + void DrawHealthBar(AActor* Actor); + + /** Hides the health bar of the specified actor. */ + void HideHealthBar(AActor* Actor); + + /** Draws all construction progress bars. */ + void DrawConstructionProgressBars(); + + /** Draws the construction progress bar of the specified actor. */ + void DrawConstructionProgressBar(AActor* Actor); + + /** Hides the construction progress bar of the specified actor. */ + void HideConstructionProgressBar(AActor* Actor); + + /** Draws a custom HUD effect for the currently hovered actor (e.g. player name). */ + void DrawHoveredActorWidget(); + + /** Draws all production progress bars. */ + void DrawProductionProgressBars(); + + /** Draws the production progress bar of the specified actor. */ + void DrawProductionProgressBar(AActor* Actor); + + /** Hides the production progress bar of the specified actor. */ + void HideProductionProgressBar(AActor* Actor); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSHoveredActorWidgetComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSHoveredActorWidgetComponent.h new file mode 100644 index 00000000..31901d5e --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSHoveredActorWidgetComponent.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "UI/RTSActorWidgetComponent.h" + +#include "RTSHoveredActorWidgetComponent.generated.h" + + +/** +* Adds a widget for showing hovered actor data to the actor. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API URTSHoveredActorWidgetComponent : public URTSActorWidgetComponent +{ + GENERATED_BODY() + +public: + /** Event when the actor was hovered. */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS") + void UpdateData(AActor* Actor); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSMinimapVolume.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSMinimapVolume.h new file mode 100644 index 00000000..4a066a0f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSMinimapVolume.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Volume.h" + +#include "RTSMinimapVolume.generated.h" + + +class UTexture2D; + + +/** +* Volume that defines the size and resolution of the minimap. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API ARTSMinimapVolume : public AVolume +{ + GENERATED_BODY() + +public: + /** Gets the Background image of the minimap. */ + UFUNCTION(BlueprintPure) + UTexture2D* GetMinimapImage() const; + +private: + /** Background image of the minimap. */ + UPROPERTY(EditInstanceOnly, Category = "RTS") + UTexture2D* MinimapImage; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSMinimapWidget.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSMinimapWidget.h new file mode 100644 index 00000000..82eb866c --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/UI/RTSMinimapWidget.h @@ -0,0 +1,132 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Blueprint/UserWidget.h" +#include "Launch/Resources/Version.h" + +#include "RTSMinimapWidget.generated.h" + + +class ARTSFogOfWarActor; +class ARTSMinimapVolume; +class ARTSPlayerController; +class ARTSVisionInfo; +class ARTSVisionVolume; + + +/** +* Widget for drawing a high-level overview of unit positions. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API URTSMinimapWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + /** Event for custom drawing of units on the minimap (e.g. for drawing hero portraits for hero units). */ + virtual void NotifyOnDrawUnit( + FPaintContext& Context, + AActor* Actor, + APlayerState* ActorOwner, + const FVector2D& MinimapPosition, + APlayerState* LocalPlayer) const; + + /** Event for custom drawing of units on the minimap (e.g. for drawing hero portraits for hero units). */ + UFUNCTION(BlueprintImplementableEvent, Category = "RTS", meta = (DisplayName = "OnDrawUnit")) + void ReceiveOnDrawUnit( + UPARAM(ref) FPaintContext& Context, + AActor* Actor, + APlayerState* ActorOwner, + const FVector2D& MinimapPosition, + APlayerState* LocalPlayer) const; + +protected: + void NativeConstruct() override; + void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 20) + int32 NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; +#else + void NativePaint(FPaintContext& InContext) const override; +#endif + + FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; + +private: + /** Brush for drawing the background of the current map. */ + UPROPERTY(EditAnywhere, Category = "RTS|Background") + FSlateBrush MinimapBackground; + + /** Brush for drawing own units on the minimap. */ + UPROPERTY(EditAnywhere, Category = "RTS|Units") + FSlateBrush OwnUnitsBrush; + + /** Brush for drawing enemy units on the minimap. */ + UPROPERTY(EditAnywhere, Category = "RTS|Units") + FSlateBrush EnemyUnitsBrush; + + /** Brush for drawing neutral units on the minimap. */ + UPROPERTY(EditAnywhere, Category = "RTS|Units") + FSlateBrush NeutralUnitsBrush; + + /** Whether to draw the minimap background layer. */ + UPROPERTY(EditAnywhere, Category = "RTS|Background") + bool bDrawBackground = true; + + /** Whether to draw unit dots on the minimap, with OwnUnitsBrush, EnemyUnitsBrush and NeutralUnitsBrush. */ + UPROPERTY(EditAnywhere, Category = "RTS|Units") + bool bDrawUnitsWithTeamColors = true; + + /** Whether to draw vision on the minimap, with UnknownAreasBrush, KnownAreasBrush and VisibleAreasBrush. */ + UPROPERTY(EditAnywhere, Category = "RTS|Vision") + bool bDrawVision = true; + + /** Whether to show the current camera frustum on the minimap. */ + UPROPERTY(EditAnywhere, Category = "RTS|Camera") + bool bDrawViewFrustum = true; + + /** Material to instance for rendering the fog of war effect. */ + UPROPERTY(EditAnywhere, Category = "RTS|Vision", meta=(AllowPrivateAccess="true")) + UMaterialInterface* FogOfWarMaterial; + + + /** Provides visibility information. */ + UPROPERTY() + ARTSFogOfWarActor* FogOfWarActor; + + /** User interface material instance for rendering fog of war on the minimap. */ + UPROPERTY() + UMaterialInstanceDynamic* FogOfWarMaterialInstance; + + /** Brush for drawing fog of war on the minimap. */ + FSlateBrush FogOfWarBrush; + + bool bMouseDown; + + UPROPERTY() + ARTSMinimapVolume* MinimapVolume; + + FVector MinimapWorldSize; + + UPROPERTY() + ARTSVisionInfo* VisionInfo; + + UPROPERTY() + ARTSVisionVolume* VisionVolume; + + void DrawBackground(FPaintContext& InContext) const; + void DrawUnits(FPaintContext& InContext) const; + void DrawVision(FPaintContext& InContext) const; + void DrawViewFrustum(FPaintContext& InContext) const; + + void DrawBoxWithBrush(FPaintContext& InContext, const FVector2D& Position, const FSlateBrush& Brush) const; + + FReply HandleMinimapClick(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent); + + FVector MinimapToWorld(const FVector2D& MinimapPosition) const; + bool ViewportToWorld(ARTSPlayerController* Player, const FVector2D& ViewportPosition, FVector& OutWorldPosition) const; + FVector2D WorldToMinimap(const FVector& WorldPosition) const; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSFogOfWarActor.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSFogOfWarActor.h new file mode 100644 index 00000000..4f745351 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSFogOfWarActor.h @@ -0,0 +1,70 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Actor.h" + +#include "RTSFogOfWarActor.generated.h" + + +class UTexture2D; +struct FUpdateTextureRegion2D; +class UMaterialInstanceDynamic; +class UMaterialInterface; +class APostProcessVolume; + +class ARTSVisionInfo; +class ARTSVisionVolume; + + +/** Renders fog of war in 3D space. */ +UCLASS() +class REALTIMESTRATEGY_API ARTSFogOfWarActor : public AActor +{ + GENERATED_BODY() + +public: + ARTSFogOfWarActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Prepares this actor for gameplay. */ + void Initialize(ARTSVisionVolume* InVisionVolume); + + void Tick(float DeltaTime) override; + + /** Gets the texture containing visibility information. */ + UTexture2D* GetFogOfWarTexture() const; + + /** Sets the vision info to render in 3D space. */ + void SetupVisionInfo(ARTSVisionInfo* VisionInfo); + +private: + /** Renders the fog of war. */ + UPROPERTY(EditInstanceOnly, Category = "RTS") + APostProcessVolume* FogOfWarVolume; + + /** Material to instance for rendering the fog of war effect. */ + UPROPERTY(EditInstanceOnly, Category = "RTS") + UMaterialInterface* FogOfWarMaterial; + + /** Provides visibility information for how to render the fog of war. */ + UPROPERTY() + ARTSVisionInfo* VisionInfo; + + /** Provides world size information for how to render the fog of war. */ + UPROPERTY() + ARTSVisionVolume* VisionVolume; + + /** Texture containing visibility information to be rendered in 3D space. */ + UPROPERTY() + UTexture2D* FogOfWarTexture; + + /** Buffer for updating the contents of the fog of war texture. */ + uint8* FogOfWarTextureBuffer; + + /** Update region for updating the contents of the fog of war texture. */ + FUpdateTextureRegion2D* FogOfWarUpdateTextureRegion; + + /** Post-process material instance for rendering fog of war in 3D space. */ + UPROPERTY() + UMaterialInstanceDynamic* FogOfWarMaterialInstance; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionComponent.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionComponent.h new file mode 100644 index 00000000..1f11e167 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionComponent.h @@ -0,0 +1,29 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Components/ActorComponent.h" + +#include "RTSVisionComponent.generated.h" + + +/** +* Allows the actor to reveal areas covered by fog of war. +*/ +UCLASS(meta = (BlueprintSpawnableComponent)) +class REALTIMESTRATEGY_API URTSVisionComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + URTSVisionComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Gets the radius in which the actor reveals areas covered by fog of war, in cm. */ + UFUNCTION(BlueprintPure) + float GetSightRadius() const; + +private: + /** Radius in which the actor reveals areas covered by fog of war, in cm. */ + UPROPERTY(EditDefaultsOnly, Category = "RTS", meta = (ClampMin = 0)) + float SightRadius; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionInfo.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionInfo.h new file mode 100644 index 00000000..2bf37028 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionInfo.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Info.h" + +#include "Vision/RTSVisionState.h" + +#include "RTSVisionInfo.generated.h" + + +class UWorld; + +class ARTSVisionVolume; + + +/** +* Defines the visible areas for a player or team. +*/ +UCLASS() +class REALTIMESTRATEGY_API ARTSVisionInfo : public AInfo +{ + GENERATED_BODY() + +public: + ARTSVisionInfo(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + /** Prepares this actor for gameplay. */ + void Initialize(ARTSVisionVolume* InVisionVolume); + + virtual void Tick(float DeltaSeconds) override; + + /** Gets the index of the team this actor keeps track of the vision for. */ + uint8 GetTeamIndex() const; + + /** Sets the index of the team this actor keeps track of the vision for. */ + void SetTeamIndex(uint8 NewTeamIndex); + + /** Gets the state of the tile with the specified coordinates. */ + ERTSVisionState GetVision(int32 X, int32 Y) const; + + /** Gets vision information for the specified team. */ + UFUNCTION(BlueprintPure, meta = (WorldContext = "WorldContextObject")) + static ARTSVisionInfo* GetVisionInfoForTeam(UObject* WorldContextObject, uint8 InTeamIndex); + +private: + /** Index of the team this actor keeps track of the vision for. */ + UPROPERTY(ReplicatedUsing = ReceivedTeamIndex) + uint8 TeamIndex; + + UPROPERTY() + ARTSVisionVolume* VisionVolume; + + /** Which tiles are currently unknown, known and visible. */ + TArray Tiles; + + bool GetTileCoordinates(int Index, int* OutX, int* OutY) const; + int32 GetTileIndex(int X, int Y) const; + + void NotifyPlayerVisionInfoAvailable(); + + UFUNCTION() + virtual void ReceivedTeamIndex(); +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionState.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionState.h new file mode 100644 index 00000000..a05a39a9 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionState.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "RTSVisionState.generated.h" + + +UENUM(BlueprintType) +enum class ERTSVisionState : uint8 +{ + /** Area has never been visited before. */ + VISION_Unknown, + + /** Area has been visited before, but is currently not. */ + VISION_Known, + + /** Area is revealed right now. */ + VISION_Visible +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionVolume.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionVolume.h new file mode 100644 index 00000000..8a290c17 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Classes/Vision/RTSVisionVolume.h @@ -0,0 +1,54 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Volume.h" + +#include "Vision/RTSVisionState.h" + +#include "RTSVisionVolume.generated.h" + + +/** +* Volume that defines the size and resolution of vision provided by units. +*/ +UCLASS(Blueprintable) +class REALTIMESTRATEGY_API ARTSVisionVolume : public AVolume +{ + GENERATED_BODY() + +public: + ARTSVisionVolume(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Prepares this actor for gameplay. */ + void Initialize(); + + /** Gets the width and height of the vision grid imposed on the world. */ + int32 GetSizeInTiles() const; + + /** Gets the size of the vision volume, in world space. */ + FVector GetSizeInWorld() const; + + /** Gets the width and height of a single vision grid tile, in cm. */ + float GetTileSize() const; + + /** Gets the minimum vision state of the world. */ + ERTSVisionState GetMinimumVisionState() const; + + FIntVector WorldToTile(const FVector& WorldPosition) const; + +private: + /** Width and height of the vision grid imposed on the world. */ + UPROPERTY(EditInstanceOnly, Category = "RTS") + int32 SizeInTiles; + + /** Minimum vision state of the world. Change this for removing the black part of fog of war, or disabling it entirely. */ + UPROPERTY(EditInstanceOnly, Category = "RTS") + ERTSVisionState MinimumVisionState; + + /** Size of the vision volume, in world space. */ + FVector SizeInWorld; + + /** Width and height of a single vision grid tile, in cm. */ + float TileSize; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSAttackComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSAttackComponent.cpp new file mode 100644 index 00000000..f7341802 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSAttackComponent.cpp @@ -0,0 +1,128 @@ +#include "Combat/RTSAttackComponent.h" + +#include "Engine/World.h" +#include "GameFramework/Pawn.h" + +#include "RTSLog.h" +#include "Combat/RTSProjectile.h" + + +URTSAttackComponent::URTSAttackComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + + // Set reasonable default values. + AcquisitionRadius = 1000.0f; + ChaseRadius = 1000.0f; + + FRTSAttackData DefaultAttack; + DefaultAttack.Cooldown = 0.5f; + DefaultAttack.Damage = 10.0f; + DefaultAttack.Range = 200.0f; + + Attacks.Add(DefaultAttack); +} + +void URTSAttackComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Update cooldown timer. + if (RemainingCooldown > 0) + { + RemainingCooldown -= DeltaTime; + + if (RemainingCooldown <= 0) + { + UE_LOG(LogRTS, Log, TEXT("Actor %s attack is ready again."), *GetOwner()->GetName()); + + // Notify listeners. + OnCooldownReady.Broadcast(GetOwner()); + } + } +} + +void URTSAttackComponent::UseAttack(int32 AttackIndex, AActor* Target) +{ + AActor* Owner = GetOwner(); + APawn* OwnerPawn = Cast(Owner); + AController* OwnerController = OwnerPawn ? OwnerPawn->GetController() : nullptr; + + if (!IsValid(Target)) + { + return; + } + + // Check cooldown. + if (RemainingCooldown > 0) + { + return; + } + + // Use attack. + UE_LOG(LogRTS, Log, TEXT("Actor %s attacks %s."), *Owner->GetName(), *Target->GetName()); + + const FRTSAttackData& Attack = Attacks[0]; + + ARTSProjectile* SpawnedProjectile = nullptr; + + if (Attack.ProjectileClass != nullptr) + { + // Fire projectile. + // Build spawn transform. + FVector SpawnLocation = Owner->GetActorLocation(); + FRotator SpawnRotation = Owner->GetActorRotation(); + FTransform SpawnTransform = FTransform(SpawnRotation, SpawnLocation); + + // Build spawn info. + FActorSpawnParameters SpawnInfo; + SpawnInfo.Instigator = OwnerPawn; + SpawnInfo.ObjectFlags |= RF_Transient; + + // Spawn projectile. + SpawnedProjectile = GetWorld()->SpawnActor(Attack.ProjectileClass, SpawnTransform, SpawnInfo); + + if (SpawnedProjectile) + { + UE_LOG(LogRTS, Log, TEXT("%s fired projectile %s at target %s."), *Owner->GetName(), *SpawnedProjectile->GetName(), *Target->GetName()); + + // Aim at target. + SpawnedProjectile->FireAt( + Target, + Attack.Damage, + Attack.DamageType, + OwnerController, + Owner); + } + } + else + { + // Deal damage immediately. + Target->TakeDamage(Attack.Damage, FDamageEvent(Attack.DamageType), OwnerController, Owner); + } + + // Start cooldown timer. + RemainingCooldown = Attack.Cooldown; + + // Notify listeners. + OnAttackUsed.Broadcast(Owner, Attack, Target, SpawnedProjectile); +} + +float URTSAttackComponent::GetAcquisitionRadius() const +{ + return AcquisitionRadius; +} + +float URTSAttackComponent::GetChaseRadius() const +{ + return ChaseRadius; +} + +TArray URTSAttackComponent::GetAttacks() const +{ + return Attacks; +} + +float URTSAttackComponent::GetRemainingCooldown() const +{ + return RemainingCooldown; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSHealthBarWidgetComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSHealthBarWidgetComponent.cpp new file mode 100644 index 00000000..2861d697 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSHealthBarWidgetComponent.cpp @@ -0,0 +1,32 @@ +#include "Combat/RTSHealthBarWidgetComponent.h" + +#include "GameFramework/Actor.h" + +#include "Combat/RTSHealthComponent.h" + + +void URTSHealthBarWidgetComponent::BeginPlay() +{ + Super::BeginPlay(); + + AActor* Owner = GetOwner(); + + if (!IsValid(Owner)) + { + return; + } + + HealthComponent = Owner->FindComponentByClass(); + + if (!IsValid(HealthComponent)) + { + return; + } + + HealthComponent->OnHealthChanged.AddDynamic(this, &URTSHealthBarWidgetComponent::OnHealthChanged); +} + +void URTSHealthBarWidgetComponent::OnHealthChanged(AActor* Actor, float OldHealth, float NewHealth, AActor* DamageCauser) +{ + UpdateHealthBar(HealthComponent->GetCurrentHealth() / HealthComponent->GetMaximumHealth()); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSHealthComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSHealthComponent.cpp new file mode 100644 index 00000000..92cc1285 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSHealthComponent.cpp @@ -0,0 +1,89 @@ +#include "Combat/RTSHealthComponent.h" + +#include "Engine/World.h" +#include "Kismet/GameplayStatics.h" +#include "Net/UnrealNetwork.h" + +#include "RTSGameMode.h" +#include "RTSLog.h" + + +URTSHealthComponent::URTSHealthComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); + + // Set reasonable default values. + CurrentHealth = 100.0f; + MaximumHealth = 100.0f; +} + +void URTSHealthComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSHealthComponent, CurrentHealth); +} + +void URTSHealthComponent::BeginPlay() +{ + Super::BeginPlay(); + + AActor* Owner = GetOwner(); + + if (!IsValid(Owner)) + { + return; + } + + Owner->OnTakeAnyDamage.AddDynamic(this, &URTSHealthComponent::OnTakeAnyDamage); +} + +float URTSHealthComponent::GetMaximumHealth() const +{ + return MaximumHealth; +} + +float URTSHealthComponent::GetCurrentHealth() const +{ + return CurrentHealth; +} + +void URTSHealthComponent::OnTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser) +{ + float OldHealth = CurrentHealth; + CurrentHealth -= Damage; + float NewHealth = CurrentHealth; + + UE_LOG(LogRTS, Log, TEXT("Actor %s has taken %f damage from %s, reducing health to %f."), + *GetOwner()->GetName(), + Damage, + *DamageCauser->GetName(), + CurrentHealth); + + // Notify listeners. + OnHealthChanged.Broadcast(GetOwner(), OldHealth, NewHealth, DamageCauser); + + // Check if we've just died. + if (CurrentHealth <= 0) + { + UE_LOG(LogRTS, Log, TEXT("Actor %s has been killed."), *GetOwner()->GetName()); + + // Get owner before destruction. + AController* OwningPlayer = Cast(GetOwner()->GetOwner()); + + // Notify listeners. + OnKilled.Broadcast(GetOwner(), OwningPlayer, DamageCauser); + + // Destroy this actor. + GetOwner()->Destroy(); + + // Notify game mode. + ARTSGameMode* GameMode = Cast(UGameplayStatics::GetGameMode(GetWorld())); + + if (GameMode != nullptr) + { + GameMode->NotifyOnActorKilled(GetOwner(), OwningPlayer); + } + } +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSProjectile.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSProjectile.cpp new file mode 100644 index 00000000..cc89022a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Combat/RTSProjectile.cpp @@ -0,0 +1,98 @@ +#include "Combat/RTSProjectile.h" + +#include "GameFramework/ProjectileMovementComponent.h" + +#include "RTSLog.h" + + +ARTSProjectile::ARTSProjectile(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryActorTick.bCanEverTick = true; + + ProjectileMovement = CreateDefaultSubobject(TEXT("ProjectileMovement")); + ProjectileMovement->bRotationFollowsVelocity = true; + ProjectileMovement->ProjectileGravityScale = 0.0f; + ProjectileMovement->InitialSpeed = 1000.0f; + + bFired = false; + + // Enable replication. + // This might change in the future, as we don't really care about exact projectile positions on client-side. + bReplicates = true; +} + +void ARTSProjectile::FireAt( + AActor* ProjectileTarget, + float ProjectileDamage, + TSubclassOf ProjectileDamageType, + AController* ProjectileEventInstigator, + AActor* ProjectileDamageCauser) +{ + if (!ProjectileTarget) + { + UE_LOG(LogRTS, Error, TEXT("No target set for projectile %s!"), *GetName()); + return; + } + + Target = ProjectileTarget; + Damage = ProjectileDamage; + DamageType = ProjectileDamageType; + EventInstigator = ProjectileEventInstigator; + DamageCauser = ProjectileDamageCauser; + + // Set direction. + FVector Direction = Target->GetActorLocation() - GetActorLocation(); + FVector DirectionNormalized = Direction.GetSafeNormal(0.01f); + + ProjectileMovement->Velocity = DirectionNormalized * ProjectileMovement->InitialSpeed; + + if (ProjectileMovement->bIsHomingProjectile) + { + // Set target. + ProjectileMovement->HomingTargetComponent = Target->GetRootComponent(); + } + + // Set time to impact. + TimeToImpact = Direction.Size() / ProjectileMovement->InitialSpeed; + bFired = true; +} + +void ARTSProjectile::Tick(float DeltaSeconds) +{ + if (!bFired) + { + return; + } + + TimeToImpact -= DeltaSeconds; + + if (TimeToImpact > 0.0f) + { + return; + } + + if (IsValid(Target)) + { + UE_LOG(LogRTS, Log, TEXT("Projectile %s hit target %s for %f damage."), *GetName(), *Target->GetName(), Damage); + + // Deal damage. + Target->TakeDamage(Damage, FDamageEvent(DamageType), EventInstigator, DamageCauser); + + // Notify listeners. + NotifyOnProjectileDetonated(Target, Damage, DamageType, EventInstigator, DamageCauser); + } + + // Destroy projectile. + Destroy(); +} + +void ARTSProjectile::NotifyOnProjectileDetonated( + AActor* ProjectileTarget, + float ProjectileDamage, + TSubclassOf ProjectileDamageType, + AController* ProjectileEventInstigator, + AActor* ProjectileDamageCauser) +{ + ReceiveOnProjectileDetonated(ProjectileTarget, ProjectileDamage, ProjectileDamageType, ProjectileEventInstigator, ProjectileDamageCauser); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSBuilderComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSBuilderComponent.cpp new file mode 100644 index 00000000..d0b65b9f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSBuilderComponent.cpp @@ -0,0 +1,201 @@ +#include "Construction/RTSBuilderComponent.h" + +#include "GameFramework/Controller.h" +#include "Kismet/GameplayStatics.h" + +#include "RTSPawnAIController.h" +#include "RTSContainerComponent.h" +#include "RTSGameMode.h" +#include "RTSLog.h" +#include "Construction/RTSConstructionSiteComponent.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Libraries/RTSGameplayLibrary.h" + + +void URTSBuilderComponent::AssignToConstructionSite(AActor* ConstructionSite) +{ + if (!ConstructionSite) + { + return; + } + + if (AssignedConstructionSite == ConstructionSite) + { + return; + } + + auto ConstructionSiteComponent = ConstructionSite->FindComponentByClass(); + + if (!ConstructionSiteComponent) + { + return; + } + + if (ConstructionSiteComponent->CanAssignBuilder(GetOwner())) + { + // Assign builder. + AssignedConstructionSite = ConstructionSite; + ConstructionSiteComponent->AssignBuilder(GetOwner()); + + // Notify listeners. + OnAssignedToConstructionSite.Broadcast(GetOwner(), ConstructionSite); + + UE_LOG(LogRTS, Log, TEXT("Builder %s assigned to construction site %s."), *GetOwner()->GetName(), *ConstructionSite->GetName()); + + if (bEnterConstructionSite) + { + // Enter construction site. + auto ContainerComponent = ConstructionSite->FindComponentByClass(); + + if (ContainerComponent) + { + ContainerComponent->LoadActor(GetOwner()); + OnConstructionSiteEntered.Broadcast(GetOwner(), ConstructionSite); + } + } + } +} + +void URTSBuilderComponent::BeginConstruction(TSubclassOf BuildingClass, const FVector& TargetLocation) +{ + // Get game, pawn and controller. + ARTSGameMode* GameMode = Cast(UGameplayStatics::GetGameMode(this)); + + if (!GameMode) + { + return; + } + + auto Pawn = Cast(GetOwner()); + + if (!Pawn) + { + return; + } + + auto PawnController = Cast(Pawn->GetController()); + + if (!PawnController) + { + return; + } + + if (BuildingClass == nullptr) + { + UE_LOG(LogRTS, Error, TEXT("Builder %s wants to build, but no building class was specified."), *GetOwner()->GetName()); + return; + } + + // Check requirements. + TSubclassOf MissingRequirement; + + if (URTSGameplayLibrary::GetMissingRequirementFor(this, GetOwner(), BuildingClass, MissingRequirement)) + { + UE_LOG(LogRTS, Error, TEXT("Builder %s wants to build %s, but is missing requirement %s."), *GetOwner()->GetName(), *BuildingClass->GetName(), *MissingRequirement->GetName()); + + // Player is missing a required actor. Stop. + PawnController->IssueStopOrder(); + return; + } + + // Move builder away in order to avoid collision. + FVector BuilderLocation = Pawn->GetActorLocation(); + FVector ToTargetLocation = TargetLocation - BuilderLocation; + ToTargetLocation.Z = 0.0f; + FVector ToTargetLocationNormalized = ToTargetLocation.GetSafeNormal(); + float SafetyDistance = + (URTSCollisionLibrary::GetActorCollisionSize(Pawn) / 2 + + URTSCollisionLibrary::GetCollisionSize(BuildingClass) / 2) + + ConstructionSiteOffset; + + FVector SafeBuilderLocation = TargetLocation - ToTargetLocationNormalized * SafetyDistance; + SafeBuilderLocation.Z = BuilderLocation.Z; + + Pawn->SetActorLocation(SafeBuilderLocation); + + // Spawn building. + AActor* Building = GameMode->SpawnActorForPlayer( + BuildingClass, + Cast(GetOwner()->GetOwner()), + FTransform(FRotator::ZeroRotator, TargetLocation)); + + if (!Building) + { + return; + } + + // Notify listeners. + OnConstructionStarted.Broadcast(Pawn, Building); + + UE_LOG(LogRTS, Log, TEXT("Builder %s has created construction site %s."), *GetOwner()->GetName(), *Building->GetName()); + + // Issue construction order. + PawnController->IssueContinueConstructionOrder(Building); +} + +void URTSBuilderComponent::BeginConstructionByIndex(int32 BuildingIndex, const FVector& TargetLocation) +{ + if (BuildingIndex < 0 || BuildingIndex >= ConstructibleBuildingClasses.Num()) + { + return; + } + + BeginConstruction(ConstructibleBuildingClasses[BuildingIndex], TargetLocation); +} + +void URTSBuilderComponent::LeaveConstructionSite() +{ + if (!AssignedConstructionSite) + { + return; + } + + auto ConstructionSite = AssignedConstructionSite; + auto ConstructionSiteComponent = ConstructionSite->FindComponentByClass(); + + if (!ConstructionSiteComponent) + { + return; + } + + // Remove builder. + AssignedConstructionSite = nullptr; + ConstructionSiteComponent->UnassignBuilder(GetOwner()); + + // Notify listeners. + OnRemovedFromConstructionSite.Broadcast(GetOwner(), ConstructionSite); + + UE_LOG(LogRTS, Log, TEXT("Builder %s has been unassigned from construction site %s."), *GetOwner()->GetName(), *ConstructionSite->GetName()); + + if (bEnterConstructionSite) + { + // Leave construction site. + auto ContainerComponent = ConstructionSite->FindComponentByClass(); + + if (ContainerComponent) + { + ContainerComponent->UnloadActor(GetOwner()); + OnConstructionSiteLeft.Broadcast(GetOwner(), ConstructionSite); + } + } +} + +TArray> URTSBuilderComponent::GetConstructibleBuildingClasses() const +{ + return ConstructibleBuildingClasses; +} + +bool URTSBuilderComponent::DoesEnterConstructionSite() const +{ + return bEnterConstructionSite; +} + +float URTSBuilderComponent::GetConstructionSiteOffset() const +{ + return ConstructionSiteOffset; +} + +AActor* URTSBuilderComponent::GetAssignedConstructionSite() const +{ + return AssignedConstructionSite; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSConstructionProgressBarWidgetComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSConstructionProgressBarWidgetComponent.cpp new file mode 100644 index 00000000..f7ad2798 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSConstructionProgressBarWidgetComponent.cpp @@ -0,0 +1,33 @@ +#include "Construction/RTSConstructionProgressBarWidgetComponent.h" + +#include "GameFramework/Actor.h" + +#include "Construction/RTSConstructionSiteComponent.h" + + +void URTSConstructionProgressBarWidgetComponent::BeginPlay() +{ + Super::BeginPlay(); + + AActor* Owner = GetOwner(); + + if (!IsValid(Owner)) + { + return; + } + + URTSConstructionSiteComponent* ConstructionSiteComponent = Owner->FindComponentByClass(); + + if (!IsValid(ConstructionSiteComponent)) + { + return; + } + + ConstructionSiteComponent->OnConstructionProgressChanged.AddDynamic(this, + &URTSConstructionProgressBarWidgetComponent::OnConstructionProgressChanged); +} + +void URTSConstructionProgressBarWidgetComponent::OnConstructionProgressChanged(AActor* Actor, float ProgressPercentage) +{ + UpdateConstructionProgressBar(ProgressPercentage); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSConstructionSiteComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSConstructionSiteComponent.cpp new file mode 100644 index 00000000..78eb0dea --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Construction/RTSConstructionSiteComponent.cpp @@ -0,0 +1,391 @@ +#include "Construction/RTSConstructionSiteComponent.h" + +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" + +#include "RTSContainerComponent.h" +#include "RTSLog.h" +#include "RTSPlayerAdvantageComponent.h" +#include "Economy/RTSPlayerResourcesComponent.h" +#include "Economy/RTSResourceType.h" +#include "Construction/RTSBuilderComponent.h" + + +URTSConstructionSiteComponent::URTSConstructionSiteComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + + SetIsReplicatedByDefault(true); + + State = ERTSConstructionState::CONSTRUCTIONSTATE_NotStarted; + + // Set reasonable default values. + ConstructionCostType = ERTSPaymentType::PAYMENT_PayImmediately; + ConstructionTime = 10.0f; + bConsumesBuilders = false; + MaxAssignedBuilders = 1; + ProgressMadeAutomatically = 0.0f; + ProgressMadePerBuilder = 1.0f; + RefundFactor = 0.5f; + bStartImmediately = true; +} + +void URTSConstructionSiteComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSConstructionSiteComponent, RemainingConstructionTime); + DOREPLIFETIME(URTSConstructionSiteComponent, State); +} + +void URTSConstructionSiteComponent::BeginPlay() +{ + Super::BeginPlay(); + + // Set container size. + auto ContainerComponent = GetOwner()->FindComponentByClass(); + + if (ContainerComponent) + { + ContainerComponent->SetCapacity(MaxAssignedBuilders); + } +} + +void URTSConstructionSiteComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + UActorComponent::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Don't update construction progress on clients - will be replicated from server. + if (GetNetMode() == NM_Client) + { + return; + } + + // Check for autostart. + if (State == ERTSConstructionState::CONSTRUCTIONSTATE_NotStarted && bStartImmediately) + { + StartConstruction(); + } + else if (State == ERTSConstructionState::CONSTRUCTIONSTATE_Finished) + { + return; + } + + // Check for speed boosts. + float SpeedBoostFactor = 1.0f; + AActor* OwningActor = GetOwner(); + + if (OwningActor) + { + AActor* OwningPlayer = OwningActor->GetOwner(); + + if (OwningPlayer) + { + URTSPlayerAdvantageComponent* PlayerAdvantageComponent = OwningPlayer->FindComponentByClass(); + + if (PlayerAdvantageComponent) + { + SpeedBoostFactor = PlayerAdvantageComponent->GetSpeedBoostFactor(); + } + } + } + + // Compute construction progress based on number of assigned builders. + float ConstructionProgress = + (DeltaTime * ProgressMadeAutomatically * SpeedBoostFactor) + + (DeltaTime * ProgressMadePerBuilder * AssignedBuilders.Num() * SpeedBoostFactor); + + UE_LOG(LogRTS, Verbose, TEXT("DeltaTime: %f, ProgressMadeAutomatically: %f, ProgressMadePerBuilder: %f, SpeedBoostFactor: %f, AssignedBuilders: %i,"), DeltaTime, ProgressMadeAutomatically, ProgressMadePerBuilder, + SpeedBoostFactor, AssignedBuilders.Num()); + + // Check construction costs. + bool bConstructionCostPaid = false; + + if (ConstructionCostType == ERTSPaymentType::PAYMENT_PayOverTime) + { + auto Owner = GetOwner()->GetOwner(); + + if (!Owner) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for construction, but has no owner."), *GetOwner()->GetName()); + return; + } + + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for construction, but has no PlayerResourcesComponent."), *Owner->GetName()); + return; + } + + bool bCanPayAllConstructionCosts = true; + + for (auto& Resource : ConstructionCosts) + { + float ResourceAmount = Resource.Value * ConstructionProgress / ConstructionTime; + + if (!PlayerResourcesComponent->CanPayResources(Resource.Key, ResourceAmount)) + { + // Construction stopped until resources become available again. + bCanPayAllConstructionCosts = false; + break; + } + } + + if (bCanPayAllConstructionCosts) + { + // Pay construction costs. + for (auto& Resource : ConstructionCosts) + { + float ResourceAmount = Resource.Value * ConstructionProgress / ConstructionTime; + PlayerResourcesComponent->PayResources(Resource.Key, ResourceAmount); + } + + bConstructionCostPaid = true; + } + } + else + { + bConstructionCostPaid = true; + } + + if (!bConstructionCostPaid) + { + return; + } + + // Update construction progress. + RemainingConstructionTime -= ConstructionProgress; + + // Check if finished. + if (RemainingConstructionTime <= 0) + { + FinishConstruction(); + } + else + { + OnConstructionProgressChanged.Broadcast(GetOwner(), GetProgressPercentage()); + } +} + +bool URTSConstructionSiteComponent::CanAssignBuilder(AActor* Builder) const +{ + return AssignedBuilders.Num() < MaxAssignedBuilders; +} + +void URTSConstructionSiteComponent::AssignBuilder(AActor* Builder) +{ + AssignedBuilders.Add(Builder); +} + +void URTSConstructionSiteComponent::UnassignBuilder(AActor* Builder) +{ + AssignedBuilders.Remove(Builder); +} + +float URTSConstructionSiteComponent::GetProgressPercentage() const +{ + return 1 - (RemainingConstructionTime / ConstructionTime); +} + +bool URTSConstructionSiteComponent::IsConstructing() const +{ + return State == ERTSConstructionState::CONSTRUCTIONSTATE_Constructing; +} + +bool URTSConstructionSiteComponent::IsFinished() const +{ + return State == ERTSConstructionState::CONSTRUCTIONSTATE_Finished; +} + +void URTSConstructionSiteComponent::StartConstruction() +{ + if (State != ERTSConstructionState::CONSTRUCTIONSTATE_NotStarted) + { + return; + } + + // Check construction cost. + if (ConstructionCostType == ERTSPaymentType::PAYMENT_PayImmediately) + { + auto Owner = GetOwner()->GetOwner(); + + if (!Owner) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for construction, but has no owner."), *GetOwner()->GetName()); + return; + } + + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for construction, but has no PlayerResourcesComponent."), *Owner->GetName()); + CancelConstruction(); + return; + } + + if (!PlayerResourcesComponent->CanPayAllResources(ConstructionCosts)) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for constructing %s, but does not have enough resources."), + *Owner->GetName(), + *GetOwner()->GetName()); + CancelConstruction(); + return; + } + + // Pay construction costs. + PlayerResourcesComponent->PayAllResources(ConstructionCosts); + } + + // Start construction. + RemainingConstructionTime = ConstructionTime; + State = ERTSConstructionState::CONSTRUCTIONSTATE_Constructing; + + UE_LOG(LogRTS, Log, TEXT("Construction %s started."), *GetOwner()->GetName()); + + // Notify listeners. + OnConstructionStarted.Broadcast(GetOwner(), ConstructionTime); +} + +void URTSConstructionSiteComponent::FinishConstruction() +{ + RemainingConstructionTime = 0; + State = ERTSConstructionState::CONSTRUCTIONSTATE_Finished; + + UE_LOG(LogRTS, Log, TEXT("Construction %s finished."), *GetOwner()->GetName()); + + // Notify builders. + if (bConsumesBuilders) + { + for (AActor* Builder : AssignedBuilders) + { + Builder->Destroy(); + OnBuilderConsumed.Broadcast(GetOwner(), Builder); + } + } + + // Notify listeners. + OnConstructionFinished.Broadcast(GetOwner()); +} + +void URTSConstructionSiteComponent::CancelConstruction() +{ + if (IsFinished()) + { + return; + } + + UE_LOG(LogRTS, Log, TEXT("Construction %s canceled."), *GetOwner()->GetName()); + + // Refund resources. + auto Owner = GetOwner()->GetOwner(); + + if (Owner) + { + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + float TimeRefundFactor = 0.0f; + + if (ConstructionCostType == ERTSPaymentType::PAYMENT_PayImmediately) + { + TimeRefundFactor = 1.0f; + } + else if (ConstructionCostType == ERTSPaymentType::PAYMENT_PayOverTime) + { + TimeRefundFactor = GetProgressPercentage(); + } + + float ActualRefundFactor = RefundFactor * TimeRefundFactor; + + // Refund construction costs. + for (auto& Resource : ConstructionCosts) + { + TSubclassOf ResourceType = Resource.Key; + float ResourceAmount = Resource.Value * ActualRefundFactor; + + PlayerResourcesComponent->AddResources(ResourceType, ResourceAmount); + + UE_LOG(LogRTS, Log, TEXT("%f %s of construction costs refunded."), ResourceAmount, *ResourceType->GetName()); + + // Notify listeners. + OnConstructionCostRefunded.Broadcast(GetOwner(), ResourceType, ResourceAmount); + } + } + } + + // Destroy construction site. + GetOwner()->Destroy(); + + // Notify listeners. + OnConstructionCanceled.Broadcast(GetOwner()); +} + +ERTSPaymentType URTSConstructionSiteComponent::GetConstructionCostType() const +{ + return ConstructionCostType; +} + +TMap, float> URTSConstructionSiteComponent::GetConstructionCosts() const +{ + return ConstructionCosts; +} + +float URTSConstructionSiteComponent::GetConstructionTime() const +{ + return ConstructionTime; +} + +bool URTSConstructionSiteComponent::ConsumesBuilders() const +{ + return bConsumesBuilders; +} + +int32 URTSConstructionSiteComponent::GetMaxAssignedBuilders() const +{ + return MaxAssignedBuilders; +} + +float URTSConstructionSiteComponent::GetProgressMadeAutomatically() const +{ + return ProgressMadeAutomatically; +} + +float URTSConstructionSiteComponent::GetProgressMadePerBuilder() const +{ + return ProgressMadePerBuilder; +} + +float URTSConstructionSiteComponent::GetRefundFactor() const +{ + return RefundFactor; +} + +bool URTSConstructionSiteComponent::DoesStartImmediately() const +{ + return bStartImmediately; +} + +ERTSConstructionState URTSConstructionSiteComponent::GetState() const +{ + return State; +} + +float URTSConstructionSiteComponent::GetRemainingConstructionTime() const +{ + return RemainingConstructionTime; +} + +TArray URTSConstructionSiteComponent::GetAssignedBuilders() const +{ + return AssignedBuilders; +} + +void URTSConstructionSiteComponent::ReceivedRemainingConstructionTime() +{ + OnConstructionProgressChanged.Broadcast(GetOwner(), GetProgressPercentage()); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSGathererComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSGathererComponent.cpp new file mode 100644 index 00000000..97c341c7 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSGathererComponent.cpp @@ -0,0 +1,444 @@ +#include "Economy/RTSGathererComponent.h" + +#include "EngineUtils.h" +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" + +#include "RTSContainerComponent.h" +#include "RTSOwnerComponent.h" +#include "RTSLog.h" +#include "Economy/RTSPlayerResourcesComponent.h" +#include "Economy/RTSResourceSourceComponent.h" +#include "Economy/RTSResourceDrainComponent.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Libraries/RTSGameplayLibrary.h" + + +URTSGathererComponent::URTSGathererComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + + SetIsReplicatedByDefault(true); + + // Set reasonable default values. + ResourceSweepRadius = 1000.0f; +} + +void URTSGathererComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSGathererComponent, CarriedResourceAmount); + DOREPLIFETIME(URTSGathererComponent, CarriedResourceType); +} + +void URTSGathererComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!CurrentResourceSource) + { + return; + } + + // Check range. + float GatherRange = GetGatherRange(CurrentResourceSource); + + if (URTSCollisionLibrary::GetActorDistance(GetOwner(), CurrentResourceSource, true) > GatherRange) + { + // Stop gathering. + CurrentResourceSource = nullptr; + return; + } + + // Update cooldown timer. + if (RemainingCooldown > 0) + { + RemainingCooldown -= DeltaTime; + + if (RemainingCooldown <= 0) + { + GatherResources(CurrentResourceSource); + } + } +} + +bool URTSGathererComponent::CanGatherFrom(AActor* ResourceSource) const +{ + if (!IsValid(ResourceSource)) + { + return false; + } + + // Check source type. + if (!ResourceSourceActorClasses.Contains(ResourceSource->GetClass())) + { + return false; + } + + // Check capacity. + FRTSGatherData GatherData; + if (!GetGatherDataForResourceSource(ResourceSource, &GatherData)) + { + return false; + } + + return CarriedResourceAmount < GatherData.Capacity; +} + +AActor* URTSGathererComponent::FindClosestResourceDrain() const +{ + // Find nearby actors. + AActor* ClosestResourceDrain = nullptr; + float ClosestResourceDrainDistance = 0.0f; + + for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) + { + auto Gatherer = GetOwner(); + auto ResourceDrain = *ActorItr; + + // Check if found resource drain. + auto ResourceDrainComponent = ResourceDrain->FindComponentByClass(); + + if (!ResourceDrainComponent) + { + continue; + } + + // Check owner. + auto GathererOwnerComponent = Gatherer->FindComponentByClass(); + + if (!GathererOwnerComponent || !GathererOwnerComponent->IsSameTeamAsActor(ResourceDrain)) + { + continue; + } + + // Check resource type. + if (!ResourceDrainComponent->GetResourceTypes().Contains(CarriedResourceType)) + { + continue; + } + + // Check if ready to use (e.g. construction finished). + if (!URTSGameplayLibrary::IsReadyToUse(ResourceDrain)) + { + continue; + } + + // Check distance. + float Distance = Gatherer->GetDistanceTo(ResourceDrain); + + if (!ClosestResourceDrain || Distance < ClosestResourceDrainDistance) + { + ClosestResourceDrain = ResourceDrain; + ClosestResourceDrainDistance = Distance; + } + } + + return ClosestResourceDrain; +} + +AActor* URTSGathererComponent::GetPreferredResourceSource() const +{ + if (IsValid(PreviousResourceSource)) + { + return PreviousResourceSource; + } + + return GetClosestResourceSource(PreviousResourceType, ResourceSweepRadius); +} + +AActor* URTSGathererComponent::GetClosestResourceSource(TSubclassOf DesiredResourceType, float MaxDistance) const +{ + // Sweep for sources. + AActor* ClosestResourceSource = nullptr; + float ClosestResourceSourceDistance = 0.0f; + + for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) + { + auto Gatherer = GetOwner(); + auto ResourceSource = *ActorItr; + + // Check if found resource source. + auto ResourceSourceComponent = ResourceSource->FindComponentByClass(); + + if (!ResourceSourceComponent) + { + continue; + } + + // Check resource type. + if (ResourceSourceComponent->GetResourceType() != DesiredResourceType) + { + continue; + } + + // Check distance. + float Distance = Gatherer->GetDistanceTo(ResourceSource); + + if (MaxDistance > 0.0f && Distance > MaxDistance) + { + continue; + } + + if (!ClosestResourceSource || Distance < ClosestResourceSourceDistance) + { + ClosestResourceSource = ResourceSource; + ClosestResourceSourceDistance = Distance; + } + } + + return ClosestResourceSource; +} + +float URTSGathererComponent::GetGatherRange(AActor* ResourceSource) const +{ + FRTSGatherData GatherData; + if (!GetGatherDataForResourceSource(ResourceSource, &GatherData)) + { + return 0.0f; + } + + return GatherData.Range; +} + +bool URTSGathererComponent::IsCarryingResources() const +{ + return CarriedResourceAmount > 0; +} + +bool URTSGathererComponent::IsGathering() const +{ + return CurrentResourceSource != nullptr; +} + +void URTSGathererComponent::StartGatheringResources(AActor* ResourceSource) +{ + if (!CanGatherFrom(ResourceSource)) + { + return; + } + + if (CurrentResourceSource == ResourceSource) + { + return; + } + + CurrentResourceSource = ResourceSource; + + // Get resource type. + auto ResourceSourceComponent = ResourceSource->FindComponentByClass(); + + if (!ResourceSourceComponent) + { + return; + } + + FRTSGatherData GatherData; + if (!GetGatherDataForResourceSource(ResourceSource, &GatherData)) + { + return; + } + + // Reset carried amount. + CarriedResourceAmount = 0.0f; + CarriedResourceType = ResourceSourceComponent->GetResourceType(); + + // Start cooldown before first gathering. + RemainingCooldown = GatherData.Cooldown; + + if (ResourceSourceComponent->MustGathererEnter()) + { + // Enter resource source. + auto ContainerComponent = ResourceSource->FindComponentByClass(); + + if (ContainerComponent) + { + ContainerComponent->LoadActor(GetOwner()); + } + } +} + +float URTSGathererComponent::GatherResources(AActor* ResourceSource) +{ + if (!IsValid(ResourceSource)) + { + return 0.0f; + } + + // Check cooldown. + if (RemainingCooldown > 0) + { + return 0.0f; + } + + // Check resource type. + FRTSGatherData GatherData; + if (!GetGatherDataForResourceSource(ResourceSource, &GatherData)) + { + return 0.0f; + } + + // Determine amount to gather. + float AmountToGather = GatherData.AmountPerGathering; + + if (CarriedResourceAmount + AmountToGather >= GatherData.Capacity) + { + // Capacity limit hit. Clamp gathered resources. + AmountToGather = GatherData.Capacity - CarriedResourceAmount; + } + + // Gather resources. + auto ResourceSourceComponent = ResourceSource->FindComponentByClass(); + float GatheredResourceAmount = ResourceSourceComponent->ExtractResources(GetOwner(), AmountToGather); + + CarriedResourceAmount += GatheredResourceAmount; + + // Start cooldown timer. + RemainingCooldown = GatherData.Cooldown; + + UE_LOG(LogRTS, Log, TEXT("Actor %s gathered %f %s from %s."), + *GetOwner()->GetName(), + GatheredResourceAmount, + *GatherData.ResourceType->GetName(), + *ResourceSource->GetName()); + + // Notify listeners. + OnResourcesGathered.Broadcast(GetOwner(), ResourceSource, GatherData, GatheredResourceAmount); + + if (GatherData.bNeedsReturnToDrain) + { + // Check if we're at capacity. + if (CarriedResourceAmount >= GatherData.Capacity) + { + // Stop gathering. + LeaveCurrentResourceSource(); + } + } + else + { + // Check if we're at capacity or resource source depleted. + if (CarriedResourceAmount >= GatherData.Capacity || !IsValid(ResourceSource)) + { + // Return immediately. + auto Owner = GetOwner()->GetOwner(); + + if (Owner) + { + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (PlayerResourcesComponent) + { + float ReturnedResources = PlayerResourcesComponent->AddResources(CarriedResourceType, CarriedResourceAmount); + + if (ReturnedResources > 0.0f) + { + CarriedResourceAmount -= ReturnedResources; + + UE_LOG(LogRTS, Log, TEXT("Actor %s returned %f %s without returning to drain."), + *GetOwner()->GetName(), + ReturnedResources, + *CarriedResourceType->GetName()); + + // Notify listeners. + OnResourcesReturned.Broadcast(GetOwner(), nullptr, CarriedResourceType, ReturnedResources); + } + } + } + } + + if (!IsValid(ResourceSource)) + { + // Stop gathering. + LeaveCurrentResourceSource(); + } + } + + return GatheredResourceAmount; +} + +float URTSGathererComponent::ReturnResources(AActor* ResourceDrain) +{ + if (!IsValid(ResourceDrain)) + { + return 0.0f; + } + + // Return resources. + auto ResourceDrainComponent = ResourceDrain->FindComponentByClass(); + float ReturnedResources = ResourceDrainComponent->ReturnResources(GetOwner(), CarriedResourceType, CarriedResourceAmount); + + CarriedResourceAmount -= ReturnedResources; + + UE_LOG(LogRTS, Log, TEXT("Actor %s returned %f %s to %s."), + *GetOwner()->GetName(), + ReturnedResources, + *CarriedResourceType->GetName(), + *ResourceDrain->GetName()); + + // Notify listeners. + OnResourcesReturned.Broadcast(GetOwner(), ResourceDrain, CarriedResourceType, ReturnedResources); + return ReturnedResources; +} + +float URTSGathererComponent::GetRemainingCooldown() const +{ + return RemainingCooldown; +} + +bool URTSGathererComponent::GetGatherDataForResourceSource(AActor* ResourceSource, FRTSGatherData* OutGatherData) const +{ + if (!IsValid(ResourceSource)) + { + return false; + } + + auto ResourceSourceComponent = ResourceSource->FindComponentByClass(); + + if (!ResourceSourceComponent) + { + return false; + } + + return GetGatherDataForResourceType(ResourceSourceComponent->GetResourceType(), OutGatherData); +} + +bool URTSGathererComponent::GetGatherDataForResourceType(TSubclassOf ResourceType, FRTSGatherData* OutGatherData) const +{ + for (const FRTSGatherData& GatherData : GatheredResources) + { + if (GatherData.ResourceType == ResourceType) + { + OutGatherData->AmountPerGathering = GatherData.AmountPerGathering; + OutGatherData->bNeedsReturnToDrain = GatherData.bNeedsReturnToDrain; + OutGatherData->Capacity = GatherData.Capacity; + OutGatherData->Cooldown = GatherData.Cooldown; + OutGatherData->Range = GatherData.Range; + OutGatherData->ResourceType = GatherData.ResourceType; + return true; + } + } + + return false; +} + +void URTSGathererComponent::LeaveCurrentResourceSource() +{ + if (!CurrentResourceSource) + { + return; + } + + // Leave resource source. + auto ContainerComponent = CurrentResourceSource->FindComponentByClass(); + + if (ContainerComponent) + { + ContainerComponent->UnloadActor(GetOwner()); + } + + // Store data about resource source for future reference (e.g. return here, or find similar). + PreviousResourceSource = CurrentResourceSource; + CurrentResourceSource = nullptr; + + PreviousResourceType = CarriedResourceType; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSPlayerResourcesComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSPlayerResourcesComponent.cpp new file mode 100644 index 00000000..c97d09c5 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSPlayerResourcesComponent.cpp @@ -0,0 +1,136 @@ +#include "Economy/RTSPlayerResourcesComponent.h" + +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" + +#include "RTSLog.h" +#include "Economy/RTSResourceType.h" + + +URTSPlayerResourcesComponent::URTSPlayerResourcesComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); +} + +void URTSPlayerResourcesComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSPlayerResourcesComponent, ResourceAmounts); +} + +void URTSPlayerResourcesComponent::BeginPlay() +{ + Super::BeginPlay(); + + // Check resource types. + int32 ResourceTypeNum = ResourceTypes.Num(); + int32 ResourceAmountNum = ResourceAmounts.Num(); + + for (int32 Index = ResourceAmountNum; Index < ResourceTypeNum; ++Index) + { + UE_LOG(LogRTS, Warning, TEXT("Starting amount for resource type %s not set for player %s, assuming zero."), + *ResourceTypes[Index]->GetName(), + *GetOwner()->GetName()); + + ResourceAmounts.Add(0); + } +} + +float URTSPlayerResourcesComponent::GetResources(TSubclassOf ResourceType) const +{ + // Get current resource amount. + int32 ResourceIndex = ResourceTypes.IndexOfByKey(ResourceType); + + if (ResourceIndex == INDEX_NONE) + { + UE_LOG(LogRTS, Error, TEXT("Unknown resource type %s for player %s."), + *ResourceType->GetName(), + *GetOwner()->GetName()); + + return 0.0f; + } + + return ResourceAmounts[ResourceIndex]; +} + +TArray> URTSPlayerResourcesComponent::GetResourceTypes() const +{ + return ResourceTypes; +} + +bool URTSPlayerResourcesComponent::CanPayResources(TSubclassOf ResourceType, float ResourceAmount) const +{ + float AvailableResources = GetResources(ResourceType); + return AvailableResources >= ResourceAmount; +} + +bool URTSPlayerResourcesComponent::CanPayAllResources(TMap, float> Resources) const +{ + for (auto& Resource : Resources) + { + if (!CanPayResources(Resource.Key, Resource.Value)) + { + return false; + } + } + + return true; +} + +float URTSPlayerResourcesComponent::AddResources(TSubclassOf ResourceType, float ResourceAmount) +{ + int32 ResourceIndex = ResourceTypes.IndexOfByKey(ResourceType); + + if (ResourceIndex == INDEX_NONE) + { + return 0.0f; + } + + // Get current resource amount. + float OldResourceAmount = GetResources(ResourceType); + + // Add resources. + float NewResourceAmount = OldResourceAmount + ResourceAmount; + ResourceAmounts[ResourceIndex] = NewResourceAmount; + + UE_LOG(LogRTS, Verbose, TEXT("Player %s stock of %s has changed to %f."), + *GetOwner()->GetName(), + *ResourceType->GetName(), + NewResourceAmount); + + // Notify listeners. + OnResourcesChanged.Broadcast(ResourceType, OldResourceAmount, NewResourceAmount, false); + return ResourceAmount; +} + +float URTSPlayerResourcesComponent::PayResources(TSubclassOf ResourceType, float ResourceAmount) +{ + // Get current resource amount. + float OldResourceAmount = GetResources(ResourceType); + + if (OldResourceAmount < ResourceAmount) + { + return 0.0f; + } + + // Deduct resources. + return AddResources(ResourceType, -ResourceAmount); +} + +void URTSPlayerResourcesComponent::PayAllResources(TMap, float> Resources) +{ + for (auto& Resource : Resources) + { + PayResources(Resource.Key, Resource.Value); + } +} + +void URTSPlayerResourcesComponent::ReceivedResourceAmounts() +{ + for (int32 Index = 0; Index < ResourceTypes.Num(); ++Index) + { + OnResourcesChanged.Broadcast(ResourceTypes[Index], 0.0f, ResourceAmounts[Index], true); + } +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSResourceDrainComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSResourceDrainComponent.cpp new file mode 100644 index 00000000..0ea5558d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSResourceDrainComponent.cpp @@ -0,0 +1,72 @@ +#include "Economy/RTSResourceDrainComponent.h" + +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" + +#include "RTSLog.h" +#include "Economy/RTSPlayerResourcesComponent.h" + + +URTSResourceDrainComponent::URTSResourceDrainComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); + + // Set reasonable default values. + GathererCapacity = 1; +} + +float URTSResourceDrainComponent::ReturnResources(AActor* Gatherer, TSubclassOf ResourceType, float ResourceAmount) +{ + // Notify player. + auto Owner = GetOwner()->GetOwner(); + + if (!Owner) + { + return 0.0f; + } + + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + return 0.0f; + } + + float ReturnedResources = PlayerResourcesComponent->AddResources(ResourceType, ResourceAmount); + + if (ReturnedResources <= 0.0f) + { + return 0.0f; + } + + // Notify listeners. + NotifyOnResourcesReturned(Gatherer, ResourceType, ReturnedResources); + return ReturnedResources; +} + +TArray> URTSResourceDrainComponent::GetResourceTypes() const +{ + return ResourceTypes; +} + +bool URTSResourceDrainComponent::MustGathererEnter() const +{ + return bGathererMustEnter; +} + +int32 URTSResourceDrainComponent::GetGathererCapacity() const +{ + return GathererCapacity; +} + +void URTSResourceDrainComponent::NotifyOnResourcesReturned_Implementation(AActor* Gatherer, TSubclassOf ResourceType, float ResourceAmount) +{ + UE_LOG(LogRTS, Log, TEXT("Actor %s has returned %f resources of type %s to %s."), + *Gatherer->GetName(), + ResourceAmount, + *ResourceType->GetName(), + *GetOwner()->GetName()); + + OnResourcesReturned.Broadcast(GetOwner(), Gatherer, ResourceType, ResourceAmount); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSResourceSourceComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSResourceSourceComponent.cpp new file mode 100644 index 00000000..6ec7dace --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Economy/RTSResourceSourceComponent.cpp @@ -0,0 +1,116 @@ +#include "Economy/RTSResourceSourceComponent.h" + +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" + +#include "RTSContainerComponent.h" +#include "RTSLog.h" + + +URTSResourceSourceComponent::URTSResourceSourceComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); + + // Set reasonable default values. + CurrentResources = 1000.0f; + MaximumResources = 1000.0f; + GatheringFactor = 1.0f; + GathererCapacity = 1; +} + +void URTSResourceSourceComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSResourceSourceComponent, CurrentResources); +} + +void URTSResourceSourceComponent::BeginPlay() +{ + Super::BeginPlay(); + + // Set container size. + auto ContainerComponent = GetOwner()->FindComponentByClass(); + + if (ContainerComponent) + { + ContainerComponent->SetCapacity(GathererCapacity); + } +} + +float URTSResourceSourceComponent::ExtractResources(AActor* Gatherer, float ResourceAmount) +{ + // Compute actual amount gathered. + float GatheredAmount = ResourceAmount * GatheringFactor; + + if (GatheredAmount > CurrentResources) + { + GatheredAmount = CurrentResources; + } + + // Deduct resources. + float OldResources = CurrentResources; + CurrentResources -= GatheredAmount; + float NewResources = CurrentResources; + + UE_LOG(LogRTS, Log, TEXT("Actor %s has gathered %f resources of type %s from %s, reducing remaining resources to %f."), + *Gatherer->GetName(), + GatheredAmount, + *ResourceType->GetName(), + *GetOwner()->GetName(), + CurrentResources); + + // Notify listeners. + OnResourcesChanged.Broadcast(GetOwner(), OldResources, NewResources); + + // Check if we're depleted. + if (CurrentResources <= 0) + { + UE_LOG(LogRTS, Log, TEXT("Actor %s has been depleted."), *GetOwner()->GetName()); + + // Notify listeners. + OnDepleted.Broadcast(GetOwner()); + + // Destroy this actor. + GetOwner()->Destroy(); + } + + return GatheredAmount; +} + +bool URTSResourceSourceComponent::CanGathererEnter(AActor* Gatherer) const +{ + auto ContainerComponent = GetOwner()->FindComponentByClass(); + return !ContainerComponent || ContainerComponent->GetContainedActors().Contains(Gatherer) || ContainerComponent->CanLoadActor(Gatherer); +} + +TSubclassOf URTSResourceSourceComponent::GetResourceType() const +{ + return ResourceType; +} + +float URTSResourceSourceComponent::GetMaximumResources() const +{ + return MaximumResources; +} + +float URTSResourceSourceComponent::GetGatheringFactor() const +{ + return GatheringFactor; +} + +bool URTSResourceSourceComponent::MustGathererEnter() const +{ + return bGathererMustEnter; +} + +int32 URTSResourceSourceComponent::GetGathererCapacity() const +{ + return GathererCapacity; +} + +float URTSResourceSourceComponent::GetCurrentResources() const +{ + return CurrentResources; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSCollisionLibrary.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSCollisionLibrary.cpp new file mode 100644 index 00000000..a48d012f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSCollisionLibrary.cpp @@ -0,0 +1,177 @@ +#include "Libraries/RTSCollisionLibrary.h" + +#include "Landscape.h" +#include "Components/ShapeComponent.h" +#include "GameFramework/Actor.h" + +#include "RTSLog.h" +#include "Libraries/RTSGameplayLibrary.h" + + +float URTSCollisionLibrary::GetActorDistance(AActor* First, AActor* Second, bool bConsiderCollisionSize) +{ + if (!First || !Second) + { + return 0.0f; + } + + float Distance = First->GetDistanceTo(Second); + + if (bConsiderCollisionSize) + { + Distance -= GetActorCollisionSize(First) / 2.0f; + Distance -= GetActorCollisionSize(Second) / 2.0f; + } + + return Distance; +} + +float URTSCollisionLibrary::GetCollisionSize(TSubclassOf ActorClass) +{ + AActor* DefaultActor = ActorClass->GetDefaultObject(); + return GetActorCollisionSize(DefaultActor) * DefaultActor->GetActorRelativeScale3D().X; +} + +float URTSCollisionLibrary::GetCollisionHeight(TSubclassOf ActorClass) +{ + AActor* DefaultActor = ActorClass->GetDefaultObject(); + return GetActorCollisionHeight(DefaultActor) * DefaultActor->GetActorRelativeScale3D().Z; +} + +float URTSCollisionLibrary::GetActorCollisionSize(AActor* Actor) +{ + if (!Actor) + { + return 0.0f; + } + + UShapeComponent* ShapeComponent = Actor->FindComponentByClass(); + return GetShapeCollisionSize(ShapeComponent); +} + +float URTSCollisionLibrary::GetActorCollisionHeight(AActor* Actor) +{ + if (!Actor) + { + return 0.0f; + } + + UShapeComponent* ShapeComponent = Actor->FindComponentByClass(); + return GetShapeCollisionHeight(ShapeComponent); +} + +float URTSCollisionLibrary::GetShapeCollisionSize(UShapeComponent* ShapeComponent) +{ + if (!ShapeComponent) + { + return 0.0f; + } + + FCollisionShape CollisionShape = ShapeComponent->GetCollisionShape(); + + if (CollisionShape.IsBox()) + { + return FMath::Max(CollisionShape.Box.HalfExtentX, CollisionShape.Box.HalfExtentY) * 2; + } + else if (CollisionShape.IsCapsule()) + { + return CollisionShape.Capsule.Radius * 2; + } + else if (CollisionShape.IsSphere()) + { + return CollisionShape.Sphere.Radius * 2; + } + else + { + UE_LOG(LogRTS, Error, TEXT("Unknown collision shape type for %s."), *ShapeComponent->GetOwner()->GetName()); + return 0.0f; + } +} + +float URTSCollisionLibrary::GetShapeCollisionHeight(UShapeComponent* ShapeComponent) +{ + if (!ShapeComponent) + { + return 0.0f; + } + + FCollisionShape CollisionShape = ShapeComponent->GetCollisionShape(); + + if (CollisionShape.IsBox()) + { + return CollisionShape.Box.HalfExtentZ * 2; + } + else if (CollisionShape.IsCapsule()) + { + return CollisionShape.Capsule.HalfHeight * 2; + } + else if (CollisionShape.IsSphere()) + { + return CollisionShape.Sphere.Radius * 2; + } + else + { + UE_LOG(LogRTS, Error, TEXT("Unknown collision shape type for %s."), *ShapeComponent->GetOwner()->GetName()); + return 0.0f; + } +} + +FVector URTSCollisionLibrary::GetGroundLocation(UObject* WorldContextObject, FVector Location) +{ + if (!WorldContextObject) + { + return Location; + } + + // Cast ray to hit world. + FCollisionObjectQueryParams Params(FCollisionObjectQueryParams::InitType::AllObjects); + TArray HitResults; + + WorldContextObject->GetWorld()->LineTraceMultiByObjectType( + HitResults, + FVector(Location.X, Location.Y, 10000.0f), + FVector(Location.X, Location.Y, -10000.0f), + Params); + + for (auto& HitResult : HitResults) + { + if (HitResult.Actor != nullptr) + { + ALandscape* Landscape = Cast(HitResult.Actor.Get()); + + if (Landscape != nullptr) + { + return HitResult.Location; + } + + continue; + } + + return HitResult.Location; + } + + return Location; +} + +bool URTSCollisionLibrary::IsSuitableLocationForActor(UWorld* World, TSubclassOf ActorClass, const FVector& Location) +{ + if (!World) + { + return false; + } + + UShapeComponent* ShapeComponent = URTSGameplayLibrary::FindDefaultComponentByClass(ActorClass); + + if (!ShapeComponent) + { + return true; + } + + FCollisionObjectQueryParams Params(FCollisionObjectQueryParams::AllDynamicObjects); + + return !World->OverlapAnyTestByObjectType( + Location, + FQuat::Identity, + Params, + ShapeComponent->GetCollisionShape()); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSGameplayLibrary.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSGameplayLibrary.cpp new file mode 100644 index 00000000..e2a1b764 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSGameplayLibrary.cpp @@ -0,0 +1,142 @@ +#include "Libraries/RTSGameplayLibrary.h" + +#include "EngineUtils.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "Engine/SCS_Node.h" +#include "Runtime/Launch/Resources/Version.h" + +#include "RTSOwnerComponent.h" +#include "RTSPlayerState.h" +#include "RTSRequirementsComponent.h" +#include "Construction/RTSConstructionSiteComponent.h" + + +UActorComponent* URTSGameplayLibrary::FindDefaultComponentByClass(const TSubclassOf InActorClass, const TSubclassOf InComponentClass) +{ + // Check CDO. + AActor* ActorCDO = InActorClass->GetDefaultObject();; + UActorComponent* FoundComponent = ActorCDO->FindComponentByClass(InComponentClass); + + if (FoundComponent) + { + return FoundComponent; + } + + // Check blueprint nodes. Components added in blueprint editor only (and not in code) are not available from CDO. + UBlueprintGeneratedClass* ActorBlueprintGeneratedClass = Cast(InActorClass); + + if (!ActorBlueprintGeneratedClass) + { + return nullptr; + } + + const TArray& ActorBlueprintNodes = ActorBlueprintGeneratedClass->SimpleConstructionScript->GetAllNodes(); + + for (USCS_Node* Node : ActorBlueprintNodes) + { + if (Node->ComponentClass->IsChildOf(InComponentClass)) + { + return Node->ComponentTemplate; + } + } + + return nullptr; +} + +bool URTSGameplayLibrary::IsAIUnit(AActor* Actor) +{ + if (!Actor) + { + return false; + } + + // Check owner. + auto OwnerComponent = Actor->FindComponentByClass(); + + if (!OwnerComponent) + { + return false; + } + + return IsOwnerABot(OwnerComponent); + +} + +bool URTSGameplayLibrary::IsReadyToUse(AActor* Actor) +{ + if (!Actor) + { + return false; + } + + auto ConstructionSiteComponent = Actor->FindComponentByClass(); + + return ConstructionSiteComponent == nullptr || ConstructionSiteComponent->IsFinished(); +} + +bool URTSGameplayLibrary::OwnerMeetsAllRequirementsFor(UObject* WorldContextObject, AActor* OwnedActor, TSubclassOf DesiredProduct) +{ + TSubclassOf MissingRequirement; + return !GetMissingRequirementFor(WorldContextObject, OwnedActor, DesiredProduct, MissingRequirement); +} + +bool URTSGameplayLibrary::GetMissingRequirementFor(UObject* WorldContextObject, AActor* OwnedActor, TSubclassOf DesiredProduct, TSubclassOf& OutMissingRequirement) +{ + if (!WorldContextObject || !OwnedActor || !OwnedActor->GetOwner()) + { + return false; + } + + if (DesiredProduct == nullptr) + { + return false; + } + + // Check owner. + URTSOwnerComponent* OwnerComponent = OwnedActor->FindComponentByClass(); + + if (OwnerComponent == nullptr) + { + return false; + } + + // Check if any requirements. + URTSRequirementsComponent* RequirementsComponent = FindDefaultComponentByClass(DesiredProduct); + + if (!RequirementsComponent || RequirementsComponent->GetRequiredActors().Num() <= 0) + { + return false; + } + + // Check if owning player owns all required actors. + TArray> RequiredActors = RequirementsComponent->GetRequiredActors(); + + for (TActorIterator ActorItr(WorldContextObject->GetWorld()); ActorItr; ++ActorItr) + { + AActor* SomeActor = *ActorItr; + URTSOwnerComponent* OtherOwnerComponent = SomeActor->FindComponentByClass(); + + if (OtherOwnerComponent && OtherOwnerComponent->GetPlayerOwner() == OwnerComponent->GetPlayerOwner() && IsReadyToUse(SomeActor)) + { + RequiredActors.Remove(SomeActor->GetClass()); + + if (RequiredActors.Num() == 0) + { + // All requirements checked. + return false; + } + } + } + + OutMissingRequirement = RequiredActors[0]; + return true; +} + +bool URTSGameplayLibrary::IsOwnerABot(URTSOwnerComponent* OwnerComponent) +{ + #if ENGINE_MINOR_VERSION < 25 + return OwnerComponent->GetPlayerOwner() && OwnerComponent->GetPlayerOwner()->bIsABot; + #else + return OwnerComponent->GetPlayerOwner() && OwnerComponent->GetPlayerOwner()->IsABot(); + #endif +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSGameplayTagLibrary.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSGameplayTagLibrary.cpp new file mode 100644 index 00000000..8a94face --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Libraries/RTSGameplayTagLibrary.cpp @@ -0,0 +1,23 @@ +#include "Libraries/RTSGameplayTagLibrary.h" + +#include "GameFramework/Actor.h" + +#include "RTSGameplayTagsComponent.h" + + +bool URTSGameplayTagLibrary::HasGameplayTag(const AActor* Actor, const FGameplayTag& Tag) +{ + if (!IsValid(Actor)) + { + return false; + } + + URTSGameplayTagsComponent* GameplayTagsComponent = Actor->FindComponentByClass(); + return IsValid(GameplayTagsComponent) ? GameplayTagsComponent->GetCurrentTags().HasTag(Tag) : false; +} + +const FGameplayTag& URTSGameplayTagLibrary::Status_Permanent_CanBeAttacked() +{ + static FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName(TEXT("Status.Permanent.CanBeAttacked"))); + return Tag; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionComponent.cpp new file mode 100644 index 00000000..23458e6c --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionComponent.cpp @@ -0,0 +1,545 @@ +#include "Production/RTSProductionComponent.h" + +#include "GameFramework/Actor.h" +#include "GameFramework/Controller.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "Engine/SCS_Node.h" +#include "Kismet/GameplayStatics.h" +#include "Net/UnrealNetwork.h" + +#include "RTSGameMode.h" +#include "RTSLog.h" +#include "RTSPlayerController.h" +#include "RTSPlayerAdvantageComponent.h" +#include "Economy/RTSPlayerResourcesComponent.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Libraries/RTSGameplayLibrary.h" +#include "Production/RTSProductionCostComponent.h" + + +URTSProductionComponent::URTSProductionComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + + SetIsReplicatedByDefault(true); + + // Set reasonable default values. + CapacityPerQueue = 5; + QueueCount = 1; +} + +void URTSProductionComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSProductionComponent, ProductionQueues); +} + +void URTSProductionComponent::BeginPlay() +{ + Super::BeginPlay(); + + // Setup queues. + for (int32 QueueIndex = 0; QueueIndex < QueueCount; ++QueueIndex) + { + FRTSProductionQueue NewQueue; + ProductionQueues.Add(NewQueue); + } +} + +void URTSProductionComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + UActorComponent::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Check for speed boosts. + float SpeedBoostFactor = 1.0f; + AActor* OwningActor = GetOwner(); + + if (OwningActor) + { + AActor* OwningPlayer = OwningActor->GetOwner(); + + if (OwningPlayer) + { + URTSPlayerAdvantageComponent* PlayerAdvantageComponent = OwningPlayer->FindComponentByClass(); + + if (PlayerAdvantageComponent) + { + SpeedBoostFactor = PlayerAdvantageComponent->GetSpeedBoostFactor(); + } + } + } + + // Process all queues. + for (int32 QueueIndex = 0; QueueIndex < QueueCount; ++QueueIndex) + { + if (ProductionQueues[QueueIndex].Num() <= 0) + { + continue; + } + + // Check production costs. + auto ProductClass = GetCurrentProduction(QueueIndex); + auto ProductionCostComponent = URTSGameplayLibrary::FindDefaultComponentByClass(ProductClass); + + bool bProductionCostPaid = false; + + if (ProductionCostComponent && ProductionCostComponent->GetProductionCostType() == ERTSPaymentType::PAYMENT_PayOverTime) + { + auto Owner = GetOwner()->GetOwner(); + + if (!Owner) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for production, but has no owner."), *GetOwner()->GetName()); + continue; + } + + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for production, but has no PlayerResourcesComponent."), *Owner->GetName()); + continue; + } + + bool bCanPayAllProductionCosts = true; + + for (auto& Resource : ProductionCostComponent->GetResources()) + { + float ResourceAmount = Resource.Value * SpeedBoostFactor * DeltaTime / ProductionCostComponent->GetProductionTime(); + + if (!PlayerResourcesComponent->CanPayResources(Resource.Key, ResourceAmount)) + { + // Production stopped until resources become available again. + bCanPayAllProductionCosts = false; + break; + } + } + + if (bCanPayAllProductionCosts) + { + // Pay production costs. + for (auto& Resource : ProductionCostComponent->GetResources()) + { + float ResourceAmount = Resource.Value * SpeedBoostFactor * DeltaTime / ProductionCostComponent->GetProductionTime(); + PlayerResourcesComponent->PayResources(Resource.Key, ResourceAmount); + } + + bProductionCostPaid = true; + } + } + else + { + bProductionCostPaid = true; + } + + if (!bProductionCostPaid) + { + continue; + } + + // Update production progress. + ProductionQueues[QueueIndex].RemainingProductionTime -= SpeedBoostFactor * DeltaTime; + + // Check if finished. + if (ProductionQueues[QueueIndex].RemainingProductionTime <= 0) + { + FinishProduction(QueueIndex); + } + else + { + OnProductionProgressChanged.Broadcast(OwningActor, QueueIndex, GetProgressPercentage(QueueIndex)); + } + } +} + +bool URTSProductionComponent::CanAssignProduction(TSubclassOf ProductClass) const +{ + return URTSGameplayLibrary::IsReadyToUse(GetOwner()) && FindQueueForProduct(ProductClass) >= 0; +} + +int32 URTSProductionComponent::FindQueueForProduct(TSubclassOf ProductClass) const +{ + // Find queue with least products that is not at capacity limit. + int32 QueueWithLeastProducts = -1; + int32 ProductsInShortestQueue = CapacityPerQueue; + + for (int32 QueueIndex = 0; QueueIndex < QueueCount; ++QueueIndex) + { + const FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + + if (Queue.Num() < ProductsInShortestQueue) + { + QueueWithLeastProducts = QueueIndex; + ProductsInShortestQueue = Queue.Num(); + } + } + + return QueueWithLeastProducts; +} + +TSubclassOf URTSProductionComponent::GetCurrentProduction(int32 QueueIndex /*= 0*/) const +{ + if (QueueIndex < 0 || QueueIndex >= QueueCount) + { + return nullptr; + } + + const FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + + return Queue.Num() > 0 ? Queue[0] : nullptr; +} + +float URTSProductionComponent::GetProductionTime(int32 QueueIndex /*= 0*/) const +{ + TSubclassOf CurrentProduction = GetCurrentProduction(QueueIndex); + + if (!CurrentProduction) + { + return 0.0f; + } + + return GetProductionTimeForProduct(CurrentProduction); +} + +float URTSProductionComponent::GetProductionTimeForProduct(TSubclassOf ProductClass) const +{ + URTSProductionCostComponent* ProductionCostComponent = + URTSGameplayLibrary::FindDefaultComponentByClass(ProductClass); + return ProductionCostComponent ? ProductionCostComponent->GetProductionTime() : 0.0f; +} + +float URTSProductionComponent::GetProgressPercentage(int32 QueueIndex /*= 0*/) const +{ + float TotalProductionTime = GetProductionTime(QueueIndex); + + if (TotalProductionTime <= 0.0f) + { + return 1.0f; + } + + float RemainingProductionTime = GetRemainingProductionTime(QueueIndex); + + if (RemainingProductionTime <= 0.0f) + { + return 1.0f; + } + + return 1 - (RemainingProductionTime / TotalProductionTime); +} + +float URTSProductionComponent::GetRemainingProductionTime(int32 QueueIndex /*= 0*/) const +{ + if (QueueIndex < 0 || QueueIndex >= QueueCount) + { + return 0.0f; + } + + return ProductionQueues[QueueIndex].RemainingProductionTime; +} + +bool URTSProductionComponent::IsProducing() const +{ + for (int32 QueueIndex = 0; QueueIndex < QueueCount; ++QueueIndex) + { + const FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + + if (Queue.Num() > 0) + { + return true; + } + } + + return false; +} + +void URTSProductionComponent::StartProduction(TSubclassOf ProductClass) +{ + // Check production state. + if (!CanAssignProduction(ProductClass)) + { + return; + } + + int32 QueueIndex = FindQueueForProduct(ProductClass); + + if (QueueIndex < 0) + { + return; + } + + // Check requirements. + TSubclassOf MissingRequirement; + + if (URTSGameplayLibrary::GetMissingRequirementFor(this, GetOwner(), ProductClass, MissingRequirement)) + { + UE_LOG(LogRTS, Error, TEXT("%s wants to produce %s, but is missing requirement %s."), *GetOwner()->GetName(), *ProductClass->GetName(), *MissingRequirement->GetName()); + return; + } + + // Check production cost. + URTSProductionCostComponent* ProductionCostComponent = + URTSGameplayLibrary::FindDefaultComponentByClass(ProductClass); + + if (ProductionCostComponent && ProductionCostComponent->GetProductionCostType() == ERTSPaymentType::PAYMENT_PayImmediately) + { + auto Owner = GetOwner()->GetOwner(); + + if (!Owner) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for production, but has no owner."), *GetOwner()->GetName()); + return; + } + + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for production, but has no PlayerResourcesComponent."), *Owner->GetName()); + return; + } + + if (!PlayerResourcesComponent->CanPayAllResources(ProductionCostComponent->GetResources())) + { + UE_LOG(LogRTS, Error, TEXT("%s needs to pay for producing %s, but does not have enough resources."), + *Owner->GetName(), + *ProductClass->GetName()); + return; + } + + // Pay production costs. + PlayerResourcesComponent->PayAllResources(ProductionCostComponent->GetResources()); + } + + // Insert into queue. + FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + Queue.Add(ProductClass); + + UE_LOG(LogRTS, Log, TEXT("%s queued %s for production in queue %i."), *GetOwner()->GetName(), *ProductClass->GetName(), QueueIndex); + + // Notify listeners. + OnProductQueued.Broadcast(GetOwner(), ProductClass, QueueIndex); + + if (Queue.Num() == 1) + { + // Start production. + StartProductionInQueue(QueueIndex); + } +} + +void URTSProductionComponent::FinishProduction(int32 QueueIndex /*= 0*/) +{ + if (QueueIndex < 0 || QueueIndex >= QueueCount) + { + return; + } + + FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + Queue.RemainingProductionTime = 0.0f; + + // Get game. + ARTSGameMode* GameMode = Cast(UGameplayStatics::GetGameMode(this)); + + if (!GameMode) + { + return; + } + + TSubclassOf ProductClass = Queue[0]; + + // Determine spawn location: Start at producing actor location. + FVector SpawnLocation = GetOwner()->GetActorLocation(); + + // Spawn next to production actor. + float SpawnOffset = 0.0f; + SpawnOffset += URTSCollisionLibrary::GetActorCollisionSize(GetOwner()) / 2; + SpawnOffset += URTSCollisionLibrary::GetCollisionSize(ProductClass) / 2; + SpawnOffset *= 1.05f; + SpawnLocation.X -= SpawnOffset; + + // Calculate location on the ground. + SpawnLocation = URTSCollisionLibrary::GetGroundLocation(this, SpawnLocation); + + // Prevent spawn collision or spawning at wrong side of the world. + SpawnLocation.Z += URTSCollisionLibrary::GetCollisionHeight(ProductClass) * 1.1f; + + // Spawn product. + AActor* Product = GameMode->SpawnActorForPlayer( + ProductClass, + Cast(GetOwner()->GetOwner()), + FTransform(FRotator::ZeroRotator, SpawnLocation)); + + if (!Product) + { + return; + } + + UE_LOG(LogRTS, Log, TEXT("%s finished producing %s in queue %i."), *GetOwner()->GetName(), *Product->GetName(), QueueIndex); + + // Notify listeners. + OnProductionFinished.Broadcast(GetOwner(), Product, QueueIndex); + + // Remove product from queue. + DequeueProduct(QueueIndex); +} + +void URTSProductionComponent::CancelProduction(int32 QueueIndex /*= 0*/, int32 ProductIndex /*= 0*/) +{ + // Get queue. + if (QueueIndex < 0 || QueueIndex >= QueueCount) + { + return; + } + + FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + + // Get product. + if (ProductIndex < 0 || ProductIndex >= Queue.Num()) + { + return; + } + + TSubclassOf ProductClass = Queue[ProductIndex]; + + // Get elapsed production time. + float TotalProductionTime = GetProductionTimeForProduct(ProductClass); + float RemainingProductionTime = ProductIndex == 0 ? ProductionQueues[QueueIndex].RemainingProductionTime : TotalProductionTime; + float ElapsedProductionTime = TotalProductionTime - RemainingProductionTime; + + UE_LOG(LogRTS, Log, TEXT("%s canceled producing product %i of class %s in queue %i after %f seconds."), + *GetOwner()->GetName(), + ProductIndex, + *ProductClass->GetName(), + QueueIndex, + ElapsedProductionTime); + + // Notify listeners. + OnProductionCanceled.Broadcast(GetOwner(), ProductClass, QueueIndex, ElapsedProductionTime); + + // Remove product from queue. + DequeueProduct(QueueIndex, ProductIndex); + + // Refund resources. + URTSProductionCostComponent* ProductionCostComponent = + URTSGameplayLibrary::FindDefaultComponentByClass(ProductClass); + + if (ProductionCostComponent) + { + auto Owner = GetOwner()->GetOwner(); + + if (!Owner) + { + return; + } + + auto PlayerResourcesComponent = Owner->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + return; + } + + float TimeRefundFactor = 0.0f; + + if (ProductionCostComponent->GetProductionCostType() == ERTSPaymentType::PAYMENT_PayImmediately) + { + TimeRefundFactor = 1.0f; + } + else if (ProductionCostComponent->GetProductionCostType() == ERTSPaymentType::PAYMENT_PayOverTime) + { + TimeRefundFactor = ElapsedProductionTime / TotalProductionTime; + } + + float ActualRefundFactor = ProductionCostComponent->GetRefundFactor() * TimeRefundFactor; + + // Refund production costs. + for (auto& Resource : ProductionCostComponent->GetResources()) + { + TSubclassOf ResourceType = Resource.Key; + float ResourceAmount = Resource.Value * ActualRefundFactor; + + PlayerResourcesComponent->AddResources(ResourceType, ResourceAmount); + + UE_LOG(LogRTS, Log, TEXT("%f %s of production costs refunded."), ResourceAmount, *ResourceType->GetName()); + + // Notify listeners. + OnProductionCostRefunded.Broadcast(GetOwner(), ResourceType, ResourceAmount); + } + } +} + +TArray> URTSProductionComponent::GetAvailableProducts() const +{ + return AvailableProducts; +} + +int32 URTSProductionComponent::GetQueueCount() const +{ + return QueueCount; +} + +int32 URTSProductionComponent::GetCapacityPerQueue() const +{ + return CapacityPerQueue; +} + +void URTSProductionComponent::DequeueProduct(int32 QueueIndex /*= 0*/, int32 ProductIndex /*= 0*/) +{ + // Get queue. + if (QueueIndex < 0 || QueueIndex >= QueueCount) + { + return; + } + + FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + + if (ProductIndex < 0 || ProductIndex >= Queue.Num()) + { + return; + } + + Queue.RemoveAt(ProductIndex); + + // Check if need to start next production. + if (ProductIndex == 0 && Queue.Num() > 0) + { + StartProductionInQueue(QueueIndex); + } +} + +void URTSProductionComponent::StartProductionInQueue(int32 QueueIndex /*= 0*/) +{ + // Get queue. + if (QueueIndex < 0 || QueueIndex >= QueueCount) + { + return; + } + + FRTSProductionQueue& Queue = ProductionQueues[QueueIndex]; + + // Get product. + if (Queue.Num() <= 0) + { + return; + } + + TSubclassOf ProductClass = Queue[0]; + + // Start production. + float ProductionTime = GetProductionTimeForProduct(ProductClass); + Queue.RemainingProductionTime = ProductionTime; + + UE_LOG(LogRTS, Log, TEXT("%s started producing %s in queue %i."), *GetOwner()->GetName(), *ProductClass->GetName(), QueueIndex); + + // Notify listeners. + OnProductionStarted.Broadcast(GetOwner(), ProductClass, QueueIndex, ProductionTime); +} + +void URTSProductionComponent::ReceivedProductionQueues() +{ + for (int32 QueueIndex = 0; QueueIndex < QueueCount; ++QueueIndex) + { + OnProductionProgressChanged.Broadcast(GetOwner(), QueueIndex, GetProgressPercentage(QueueIndex)); + } +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionCostComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionCostComponent.cpp new file mode 100644 index 00000000..b18b5f1a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionCostComponent.cpp @@ -0,0 +1,33 @@ +#include "Production/RTSProductionCostComponent.h" + +#include "Economy/RTSResourceType.h" + + +URTSProductionCostComponent::URTSProductionCostComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + ProductionCostType = ERTSPaymentType::PAYMENT_PayImmediately; + ProductionTime = 5.0f; + RefundFactor = 0.5f; +} + +ERTSPaymentType URTSProductionCostComponent::GetProductionCostType() const +{ + return ProductionCostType; +} + +float URTSProductionCostComponent::GetProductionTime() const +{ + return ProductionTime; +} + +TMap, float> URTSProductionCostComponent::GetResources() const +{ + return Resources; +} + +float URTSProductionCostComponent::GetRefundFactor() const +{ + return RefundFactor; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionProgressBarWidgetComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionProgressBarWidgetComponent.cpp new file mode 100644 index 00000000..43c75e8a --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionProgressBarWidgetComponent.cpp @@ -0,0 +1,32 @@ +#include "Production/RTSProductionProgressBarWidgetComponent.h" + +#include "GameFramework/Actor.h" + +#include "Production/RTSProductionComponent.h" + +void URTSProductionProgressBarWidgetComponent::BeginPlay() +{ + Super::BeginPlay(); + + AActor* Owner = GetOwner(); + + if (!IsValid(Owner)) + { + return; + } + + URTSProductionComponent* ProductionComponent = Owner->FindComponentByClass(); + + if (!IsValid(ProductionComponent)) + { + return; + } + + ProductionComponent->OnProductionProgressChanged.AddDynamic(this, + &URTSProductionProgressBarWidgetComponent::OnProductionProgressChanged); +} + +void URTSProductionProgressBarWidgetComponent::OnProductionProgressChanged(AActor* Actor, int32 QueueIndex, float ProgressPercentage) +{ + UpdateProductionProgressBar(QueueIndex, ProgressPercentage); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionQueue.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionQueue.cpp new file mode 100644 index 00000000..819f562b --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Production/RTSProductionQueue.cpp @@ -0,0 +1,30 @@ +#include "Production/RTSProductionQueue.h" + + +TSubclassOf FRTSProductionQueue::operator[](int32 Index) const +{ + return Queue[Index]; +} + +void FRTSProductionQueue::Add(TSubclassOf Product) +{ + Queue.Add(Product); +} + +int32 FRTSProductionQueue::Num() const +{ + return Queue.Num(); +} + +void FRTSProductionQueue::RemoveAt(int32 Index) +{ + // Advance all products. + int32 Slots = Queue.Num(); + + for (int32 SlotToUpdate = Index; SlotToUpdate < Slots - 1; ++SlotToUpdate) + { + Queue[SlotToUpdate] = Queue[SlotToUpdate + 1]; + } + + Queue.RemoveAt(Slots - 1); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSCheatManager.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSCheatManager.cpp new file mode 100644 index 00000000..124d2181 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSCheatManager.cpp @@ -0,0 +1,133 @@ +#include "RTSCheatManager.h" + +#include "EngineUtils.h" +#include "GameFramework/Controller.h" +#include "GameFramework/Pawn.h" +#include "Kismet/GameplayStatics.h" + +#include "RTSGameMode.h" +#include "RTSLog.h" +#include "RTSPlayerController.h" +#include "RTSPlayerAIController.h" +#include "RTSPlayerAdvantageComponent.h" +#include "Economy/RTSPlayerResourcesComponent.h" + + +URTSCheatManager::URTSCheatManager(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) +{ + // Set reasonable default values. + ResourceAmount = 1000.0f; + SpeedBoostFactor = 10.0f; +} + +void URTSCheatManager::Boost() +{ + APlayerController* Player = GetOuterAPlayerController(); + + if (!Player) + { + return; + } + + URTSPlayerAdvantageComponent* PlayerAdvantageComponent = Player->FindComponentByClass(); + + if (!PlayerAdvantageComponent) + { + return; + } + + PlayerAdvantageComponent->SetSpeedBoostFactor(PlayerAdvantageComponent->GetSpeedBoostFactor() * SpeedBoostFactor); + UE_LOG(LogRTS, Log, TEXT("Cheat: Set speed boost factor to %f."), PlayerAdvantageComponent->GetSpeedBoostFactor()); +} + +void URTSCheatManager::God() +{ + Super::God(); + + APlayerController* Player = GetOuterAPlayerController(); + + if (!Player) + { + return; + } + + URTSPlayerAdvantageComponent* PlayerAdvantageComponent = Player->FindComponentByClass(); + + if (!PlayerAdvantageComponent) + { + return; + } + + // Toggle god mode. + PlayerAdvantageComponent->SetGodModeEnabled(!PlayerAdvantageComponent->IsGodModeEnabled()); + + for (TActorIterator PawnItr(GetWorld()); PawnItr; ++PawnItr) + { + APawn* Pawn = *PawnItr; + + if (!IsValid(Pawn) || Pawn->GetOwner() != Player) + { + continue; + } + + Pawn->SetCanBeDamaged(!PlayerAdvantageComponent->IsGodModeEnabled()); + } +} + +void URTSCheatManager::Money() +{ + APlayerController* Player = GetOuterAPlayerController(); + + if (!Player) + { + return; + } + + URTSPlayerResourcesComponent* PlayerResourcesComponent = Player->FindComponentByClass(); + + if (!PlayerResourcesComponent) + { + return; + } + + for (TSubclassOf ResourceType : ResourceTypes) + { + PlayerResourcesComponent->AddResources(ResourceType, ResourceAmount); + UE_LOG(LogRTS, Log, TEXT("Cheat: Added %f %s."), ResourceAmount, *ResourceType->GetName()); + } +} + +void URTSCheatManager::Victory() +{ + APlayerController* Player = GetOuterAPlayerController(); + + if (!Player) + { + return; + } + + ARTSGameMode* GameMode = Cast(UGameplayStatics::GetGameMode(Player)); + + if (!GameMode) + { + return; + } + + for (TActorIterator ControllerItr(GetWorld()); ControllerItr; ++ControllerItr) + { + AController* Controller = *ControllerItr; + + if (!IsValid(Controller) || Controller == Player) + { + continue; + } + + if (Cast(Controller) == nullptr && + Cast (Controller) == nullptr) + { + continue; + } + + GameMode->NotifyOnPlayerDefeated(Controller); + } +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSContainerComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSContainerComponent.cpp new file mode 100644 index 00000000..c0cc259f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSContainerComponent.cpp @@ -0,0 +1,78 @@ +#include "RTSContainerComponent.h" + +#include "GameFramework/Actor.h" + +#include "RTSLog.h" + + +URTSContainerComponent::URTSContainerComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + Capacity = 1; +} + +bool URTSContainerComponent::CanLoadActor(AActor* Actor) const +{ + return Capacity < 0 || ContainedActors.Num() < Capacity; +} + +void URTSContainerComponent::LoadActor(AActor* Actor) +{ + if (!CanLoadActor(Actor)) + { + return; + } + + if (ContainedActors.Contains(Actor)) + { + return; + } + + // Add to container. + ContainedActors.Add(Actor); + + // Hide actor. + Actor->SetActorHiddenInGame(true); + Actor->SetActorEnableCollision(false); + + // Notify listeners. + OnActorEntered.Broadcast(GetOwner(), Actor); + + UE_LOG(LogRTS, Log, TEXT("Actor %s has entered %s."), *Actor->GetName(), *GetOwner()->GetName()); +} + +void URTSContainerComponent::UnloadActor(AActor* Actor) +{ + if (!ContainedActors.Contains(Actor)) + { + return; + } + + // Remove from container. + ContainedActors.Remove(Actor); + + // Show actor. + Actor->SetActorHiddenInGame(false); + Actor->SetActorEnableCollision(true); + + // Notify listeners. + OnActorLeft.Broadcast(GetOwner(), Actor); + + UE_LOG(LogRTS, Log, TEXT("Actor %s has left %s."), *Actor->GetName(), *GetOwner()->GetName()); +} + +int32 URTSContainerComponent::GetCapacity() const +{ + return Capacity; +} + +void URTSContainerComponent::SetCapacity(int32 InCapacity) +{ + Capacity = InCapacity; +} + +TArray URTSContainerComponent::GetContainedActors() const +{ + return ContainedActors; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSDescriptionComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSDescriptionComponent.cpp new file mode 100644 index 00000000..b37f5552 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSDescriptionComponent.cpp @@ -0,0 +1,6 @@ +#include "RTSDescriptionComponent.h" + +FText URTSDescriptionComponent::GetDescription() const +{ + return Description; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameMode.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameMode.cpp new file mode 100644 index 00000000..00a16504 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameMode.cpp @@ -0,0 +1,382 @@ +#include "RTSGameMode.h" + +#include "EngineUtils.h" +#include "Engine/World.h" +#include "GameFramework/Controller.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/PlayerState.h" +#include "Kismet/GameplayStatics.h" + +#include "RTSGameState.h" +#include "RTSLog.h" +#include "RTSOwnerComponent.h" +#include "RTSPlayerAIController.h" +#include "RTSPlayerAdvantageComponent.h" +#include "RTSPlayerController.h" +#include "RTSPlayerStart.h" +#include "RTSPlayerState.h" +#include "RTSTeamInfo.h" +#include "Construction/RTSConstructionSiteComponent.h" +#include "Vision/RTSVisionInfo.h" + + +ARTSGameMode::ARTSGameMode(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + TeamClass = ARTSTeamInfo::StaticClass(); + NumTeams = 2; +} + +void ARTSGameMode::BeginPlay() +{ + Super::BeginPlay(); + + // Parse options. + FString NumAIPlayersString = UGameplayStatics::ParseOption(OptionsString, TEXT("NumAIPlayers")); + + if (!NumAIPlayersString.IsEmpty()) + { + NumAIPlayers = FCString::Atoi(*NumAIPlayersString); + } + + UE_LOG(LogRTS, Log, TEXT("NumAIPlayers = %i"), NumAIPlayers); + + // Spawn AI players. + for (int32 Index = 0; Index < NumAIPlayers; ++Index) + { + ARTSPlayerAIController* NewAI = StartAIPlayer(); + + if (NewAI != nullptr) + { + NewAI->PlayerState->SetPlayerName(FString::Printf(TEXT("AI Player %i"), Index + 1)); + } + } +} + +void ARTSGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) +{ + AGameModeBase::InitGame(MapName, Options, ErrorMessage); + + // Set up teams. + if (TeamClass == nullptr) + { + TeamClass = ARTSTeamInfo::StaticClass(); + } + + for (uint8 TeamIndex = 0; TeamIndex < NumTeams; ++TeamIndex) + { + // Add team. + ARTSTeamInfo* NewTeam = GetWorld()->SpawnActor(TeamClass); + NewTeam->SetTeamIndex(TeamIndex); + + if (Teams.Num() <= TeamIndex) + { + Teams.SetNum(TeamIndex + 1); + } + + Teams[TeamIndex] = NewTeam; + + // Setup vision. + ARTSVisionInfo* TeamVision = GetWorld()->SpawnActor(); + TeamVision->SetTeamIndex(TeamIndex); + + UE_LOG(LogRTS, Log, TEXT("Set up team %s (team index %i)."), *NewTeam->GetName(), TeamIndex); + } +} + +ARTSPlayerStart* ARTSGameMode::FindRTSPlayerStart(AController* Player) +{ + // Choose a player start. + TArray UnOccupiedStartPoints; + TArray OccupiedStartPoints; + + for (TActorIterator It(GetWorld()); It; ++It) + { + ARTSPlayerStart* PlayerStart = *It; + + if (PlayerStart->GetPlayer() == nullptr) + { + UnOccupiedStartPoints.Add(PlayerStart); + } + else + { + OccupiedStartPoints.Add(PlayerStart); + } + } + + if (UnOccupiedStartPoints.Num() > 0) + { + return UnOccupiedStartPoints[FMath::RandRange(0, UnOccupiedStartPoints.Num() - 1)]; + } + else if (OccupiedStartPoints.Num() > 0) + { + return OccupiedStartPoints[FMath::RandRange(0, OccupiedStartPoints.Num() - 1)]; + } + + return nullptr; +} + +void ARTSGameMode::RestartPlayer(AController* NewPlayer) +{ + if (NewPlayer == nullptr || NewPlayer->IsPendingKillPending()) + { + return; + } + + ARTSPlayerStart* StartSpot = FindRTSPlayerStart(NewPlayer); + RestartPlayerAtPlayerStart(NewPlayer, StartSpot); +} + +void ARTSGameMode::RestartPlayerAtPlayerStart(AController* NewPlayer, AActor* StartSpot) +{ + // Spawn default pawns (most likely, this will be the camera pawn in our case). + AGameModeBase::RestartPlayerAtPlayerStart(NewPlayer, StartSpot); + + // Verify parameters. + if (NewPlayer == nullptr || NewPlayer->IsPendingKillPending()) + { + return; + } + + if (!StartSpot) + { + return; + } + + ARTSGameState* RTSGameState = GetGameState(); + + if (!IsValid(RTSGameState)) + { + return; + } + + // Occupy start spot. + ARTSPlayerStart* PlayerStart = Cast(StartSpot); + + if (PlayerStart) + { + UE_LOG(LogRTS, Log, TEXT("Start spot %s is now occupied by player %s."), *PlayerStart->GetName(), *NewPlayer->GetName()); + PlayerStart->SetPlayer(NewPlayer); + } + + // Set team. + if (PlayerStart->GetTeamIndex() >= Teams.Num()) + { + UE_LOG(LogRTS, Warning, TEXT("Player start team index is %d, but game only has %d teams."), PlayerStart->GetTeamIndex(), Teams.Num()); + } + else + { + Teams[PlayerStart->GetTeamIndex()]->AddToTeam(NewPlayer); + } + + // Build spawn transform. + // Don't allow initial actors to be spawned with any pitch or roll. + FRotator SpawnRotation(ForceInit); + SpawnRotation.Yaw = StartSpot->GetActorRotation().Yaw; + + // Build spawn info. + for (int32 Index = 0; Index < InitialActors.Num(); ++Index) + { + TSubclassOf ActorClass = InitialActors[Index]; + + // Spawn actor. + FVector SpawnLocation = StartSpot->GetActorLocation(); + + if (Index < InitialActorLocations.Num()) + { + SpawnLocation += InitialActorLocations[Index]; + } + + FTransform SpawnTransform = FTransform(SpawnRotation, SpawnLocation); + AActor* SpawnedActor = SpawnActorForPlayer(ActorClass, NewPlayer, SpawnTransform); + + // Finish construction of initial buildings immediately. + if (SpawnedActor != nullptr) + { + URTSConstructionSiteComponent* ConstructionSiteComponent = SpawnedActor->FindComponentByClass(); + + if (ConstructionSiteComponent != nullptr) + { + ConstructionSiteComponent->FinishConstruction(); + } + } + } + + // Transfer ownership of pre-placed units. + ARTSPlayerState* PlayerState = Cast(NewPlayer->PlayerState); + + if (IsValid(PlayerState)) + { + uint8 PlayerIndex = GetAvailablePlayerIndex(); + PlayerState->SetPlayerIndex(PlayerIndex); + + for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) + { + AActor* Actor = *ActorItr; + + // Check owner. + URTSOwnerComponent* OwnerComponent = Actor->FindComponentByClass(); + + if (IsValid(OwnerComponent) && OwnerComponent->GetInitialOwnerPlayerIndex() == PlayerIndex) + { + TransferOwnership(Actor, NewPlayer); + } + } + } +} + +ARTSPlayerAIController* ARTSGameMode::StartAIPlayer() +{ + FActorSpawnParameters SpawnInfo; + SpawnInfo.Instigator = GetInstigator(); + SpawnInfo.ObjectFlags |= RF_Transient; // We never want to save player controllers into a map + SpawnInfo.bDeferConstruction = true; + ARTSPlayerAIController* NewAI = GetWorld()->SpawnActor(PlayerAIControllerClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo); + if (NewAI) + { + UGameplayStatics::FinishSpawningActor(NewAI, FTransform(FRotator::ZeroRotator, FVector::ZeroVector)); + + UE_LOG(LogRTS, Log, TEXT("Spawned AI player %s."), *NewAI->GetName()); + } + else + { + UE_LOG(LogRTS, Error, TEXT("Failed to spawn AI player.")); + return nullptr; + } + + RestartPlayer(NewAI); + return NewAI; +} + +AActor* ARTSGameMode::SpawnActorForPlayer(TSubclassOf ActorClass, AController* ActorOwner, const FTransform& SpawnTransform) +{ + // Spawn actor. + FActorSpawnParameters SpawnParams; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; + + AActor* SpawnedActor = GetWorld()->SpawnActor(ActorClass->GetDefaultObject()->GetClass(), SpawnTransform, SpawnParams); + + // Set owning player. + if (SpawnedActor && ActorOwner) + { + UE_LOG(LogRTS, Log, TEXT("Spawned %s for player %s at %s."), *SpawnedActor->GetName(), *ActorOwner->GetName(), *SpawnTransform.GetLocation().ToString()); + + // Set owning player. + TransferOwnership(SpawnedActor, ActorOwner); + } + + return SpawnedActor; +} + +void ARTSGameMode::TransferOwnership(AActor* Actor, AController* NewOwner) +{ + if (!Actor || !NewOwner) + { + return; + } + + // Set owning player. + Actor->SetOwner(NewOwner); + + URTSOwnerComponent* OwnerComponent = Actor->FindComponentByClass(); + + if (OwnerComponent) + { + OwnerComponent->SetPlayerOwner(NewOwner); + } + + UE_LOG(LogRTS, Log, TEXT("Player %s is now owning %s."), *NewOwner->GetName(), *Actor->GetName()); + + // Check for god mode. + URTSPlayerAdvantageComponent* PlayerAdvantageComponent = NewOwner->FindComponentByClass(); + + if (PlayerAdvantageComponent) + { + APawn* Pawn = Cast(Actor); + + if (Pawn) + { + Pawn->SetCanBeDamaged(!PlayerAdvantageComponent->IsGodModeEnabled()); + } + } + + // Notify listeners. + ARTSPlayerController* NewPlayerOwner = Cast(NewOwner); + + if (NewPlayerOwner != nullptr) + { + NewPlayerOwner->NotifyOnActorOwnerChanged(Actor); + } +} + +void ARTSGameMode::NotifyOnActorKilled(AActor* Actor, AController* ActorOwner) +{ + if (DefeatConditionActorClasses.Num() <= 0) + { + return; + } + + ARTSPlayerController* OwningPlayer = Cast(ActorOwner); + + if (OwningPlayer == nullptr) + { + ARTSPlayerAIController* OwningAIPlayer = Cast(ActorOwner); + + if (OwningAIPlayer == nullptr) + { + return; + } + } + + // Check if any required actors are still alive. + for (AActor* OwnedActor : ActorOwner->Children) + { + if (DefeatConditionActorClasses.Contains(OwnedActor->GetClass())) + { + return; + } + } + + UE_LOG(LogRTS, Log, TEXT("Player %s does not control any required actors anymore and has been defeated."), *ActorOwner->GetName()); + + // Notify listeners. + NotifyOnPlayerDefeated(ActorOwner); +} + +void ARTSGameMode::NotifyOnPlayerDefeated(AController* Player) +{ + ReceiveOnPlayerDefeated(Player); +} + +uint8 ARTSGameMode::GetAvailablePlayerIndex() +{ + UWorld* World = GetWorld(); + + if (!IsValid(World)) + { + return ARTSPlayerState::PLAYER_INDEX_NONE; + } + + uint8 PlayerIndex = 0; + bool bPlayerIndexInUse = false; + + do + { + bPlayerIndexInUse = false; + + for (TActorIterator PlayerIt(World); PlayerIt; ++PlayerIt) + { + ARTSPlayerState* PlayerState = *PlayerIt; + + if (PlayerState->GetPlayerIndex() == PlayerIndex) + { + bPlayerIndexInUse = true; + ++PlayerIndex; + break; + } + } + } while (bPlayerIndexInUse && PlayerIndex < ARTSPlayerState::PLAYER_INDEX_NONE); + + return PlayerIndex; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameState.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameState.cpp new file mode 100644 index 00000000..7b069218 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameState.cpp @@ -0,0 +1,56 @@ +#include "RTSGameState.h" + +#include "EngineUtils.h" +#include "GameFramework/Actor.h" + +#include "Vision/RTSFogOfWarActor.h" +#include "Vision/RTSVisionInfo.h" +#include "Vision/RTSVisionVolume.h" + +void ARTSGameState::HandleBeginPlay() +{ + // Make sure all actors have begun play. + Super::HandleBeginPlay(); + + // Setup vision. + ARTSFogOfWarActor* FogOfWarActor = nullptr; + TArray VisionInfos; + ARTSVisionVolume* VisionVolume = nullptr; + + for (TActorIterator It(GetWorld()); It; ++It) + { + AActor* Actor = *It; + + if (IsValid(Actor)) + { + if (Actor->IsA(ARTSFogOfWarActor::StaticClass())) + { + FogOfWarActor = Cast(Actor); + } + else if (Actor->IsA(ARTSVisionInfo::StaticClass())) + { + ARTSVisionInfo* VisionInfo = Cast(Actor); + VisionInfos.Add(VisionInfo); + } + else if (Actor->IsA(ARTSVisionVolume::StaticClass())) + { + VisionVolume = Cast(Actor); + } + } + } + + if (IsValid(VisionVolume)) + { + VisionVolume->Initialize(); + } + + for (ARTSVisionInfo* VisionInfo : VisionInfos) + { + VisionInfo->Initialize(VisionVolume); + } + + if (IsValid(FogOfWarActor)) + { + FogOfWarActor->Initialize(VisionVolume); + } +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameplayTagsComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameplayTagsComponent.cpp new file mode 100644 index 00000000..01821b2f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSGameplayTagsComponent.cpp @@ -0,0 +1,94 @@ +#include "RTSGameplayTagsComponent.h" + +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" + +#include "RTSLog.h" + +URTSGameplayTagsComponent::URTSGameplayTagsComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); +} + +void URTSGameplayTagsComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSGameplayTagsComponent, CurrentTags); +} + +void URTSGameplayTagsComponent::BeginPlay() +{ + Super::BeginPlay(); + + for (const FGameplayTag& Tag : InitialTags) + { + CurrentTags.AddTagFast(Tag); + + UE_LOG(LogRTS, Log, TEXT("Added initial gameplay tag %s for %s."), *Tag.ToString(), *GetOwner()->GetName()); + } +} + +void URTSGameplayTagsComponent::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + TagContainer.AppendTags(CurrentTags); +} + +FGameplayTagContainer URTSGameplayTagsComponent::GetCurrentTags() const +{ + FGameplayTagContainer GameplayTagContainer; + GetOwnedGameplayTags(GameplayTagContainer); + return GameplayTagContainer; +} + +void URTSGameplayTagsComponent::AddGameplayTag(const FGameplayTag& NewTag) +{ + CurrentTags.AddTag(NewTag); + + UE_LOG(LogRTS, Log, TEXT("Added gameplay tag %s for %s."), *NewTag.ToString(), *GetOwner()->GetName()); + + NotifyOnCurrentTagsChanged(); +} + +void URTSGameplayTagsComponent::AddGameplayTags(const FGameplayTagContainer& NewTags) +{ + CurrentTags.AppendTags(NewTags); + + UE_LOG(LogRTS, Log, TEXT("Added gameplay tags %s for %s."), *NewTags.ToString(), *GetOwner()->GetName()); + + NotifyOnCurrentTagsChanged(); +} + +bool URTSGameplayTagsComponent::RemoveGameplayTag(const FGameplayTag& TagToRemove) +{ + bool bRemoved = CurrentTags.RemoveTag(TagToRemove); + + if (bRemoved) + { + UE_LOG(LogRTS, Log, TEXT("Removed gameplay tag %s from %s."), *TagToRemove.ToString(), *GetOwner()->GetName()); + + NotifyOnCurrentTagsChanged(); + } + + return bRemoved; +} + +void URTSGameplayTagsComponent::RemoveGameplayTags(const FGameplayTagContainer& TagsToRemove) +{ + CurrentTags.RemoveTags(TagsToRemove); + + UE_LOG(LogRTS, Log, TEXT("Removed gameplay tags %s from %s."), *TagsToRemove.ToString(), *GetOwner()->GetName()); + + NotifyOnCurrentTagsChanged(); +} + +void URTSGameplayTagsComponent::NotifyOnCurrentTagsChanged() +{ + CurrentTagsChanged.Broadcast(GetOwner(), CurrentTags); +} + +void URTSGameplayTagsComponent::ReceivedCurrentTags() +{ + NotifyOnCurrentTagsChanged(); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSLog.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSLog.cpp new file mode 100644 index 00000000..79c5223d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSLog.cpp @@ -0,0 +1,3 @@ +#include "RTSLog.h" + +DEFINE_LOG_CATEGORY(LogRTS); diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSNameComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSNameComponent.cpp new file mode 100644 index 00000000..0eb620bf --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSNameComponent.cpp @@ -0,0 +1,6 @@ +#include "RTSNameComponent.h" + +FText URTSNameComponent::GetName() const +{ + return Name; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSOwnerComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSOwnerComponent.cpp new file mode 100644 index 00000000..240ff6bf --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSOwnerComponent.cpp @@ -0,0 +1,88 @@ +#include "RTSOwnerComponent.h" + +#include "GameFramework/Controller.h" +#include "Net/UnrealNetwork.h" + +#include "RTSPlayerState.h" +#include "RTSTeamInfo.h" + + +URTSOwnerComponent::URTSOwnerComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); + + InitialOwnerPlayerIndex = ARTSPlayerState::PLAYER_INDEX_NONE; +} + +void URTSOwnerComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(URTSOwnerComponent, PlayerOwner); +} + + +ARTSPlayerState* URTSOwnerComponent::GetPlayerOwner() const +{ + return PlayerOwner; +} + +void URTSOwnerComponent::SetPlayerOwner(AController* Controller) +{ + ARTSPlayerState* PreviousOwner = PlayerOwner; + + if (!Controller) + { + PlayerOwner = nullptr; + } + else + { + PlayerOwner = Cast(Controller->PlayerState); + } + + if (PlayerOwner != PreviousOwner) + { + // Notify listeners. + OnOwnerChanged.Broadcast(GetOwner(), Controller); + } +} + +bool URTSOwnerComponent::IsSameTeamAsActor(AActor* Other) const +{ + if (!Other) + { + return false; + } + + ARTSPlayerState* MyOwner = GetPlayerOwner(); + + if (!MyOwner) + { + return false; + } + + URTSOwnerComponent* OtherOwnerComponent = Other->FindComponentByClass(); + + if (!OtherOwnerComponent) + { + return false; + } + + ARTSPlayerState* OtherOwner = OtherOwnerComponent->GetPlayerOwner(); + + return MyOwner->IsSameTeamAs(OtherOwner); +} + +bool URTSOwnerComponent::IsSameTeamAsController(AController* C) const +{ + ARTSPlayerState* MyOwner = GetPlayerOwner(); + ARTSPlayerState* OtherPlayer = Cast(C->PlayerState); + + return MyOwner && MyOwner->IsSameTeamAs(OtherPlayer); +} + +uint8 URTSOwnerComponent::GetInitialOwnerPlayerIndex() +{ + return InitialOwnerPlayerIndex; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPawnAIController.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPawnAIController.cpp new file mode 100644 index 00000000..9277ceb3 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPawnAIController.cpp @@ -0,0 +1,345 @@ +#include "RTSPawnAIController.h" + +#include "BehaviorTree/BehaviorTreeComponent.h" +#include "BehaviorTree/BlackboardComponent.h" + +#include "RTSLog.h" +#include "RTSOwnerComponent.h" +#include "Economy/RTSGathererComponent.h" +#include "Combat/RTSAttackComponent.h" +#include "Construction/RTSBuilderComponent.h" +#include "Libraries/RTSGameplayTagLibrary.h" + + +void ARTSPawnAIController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + AttackComponent = InPawn->FindComponentByClass(); + + // Make AI use assigned blackboard. + UBlackboardComponent* BlackboardComponent; + + if (UseBlackboard(PawnBlackboardAsset, BlackboardComponent)) + { + // Setup blackboard. + IssueStopOrder(); + } + + // Run behavior tree. + RunBehaviorTree(PawnBehaviorTreeAsset); +} + +void ARTSPawnAIController::FindTargetInAcquisitionRadius() +{ + if (!AttackComponent) + { + return; + } + + // Find nearby actors. + TArray HitResults; + TraceSphere(GetPawn()->GetActorLocation(), AttackComponent->GetAcquisitionRadius(), GetPawn(), ECC_Pawn, HitResults); + + // Find target to acquire. + for (auto& HitResult : HitResults) + { + if (HitResult.Actor == nullptr) + { + continue; + } + + if (HitResult.Actor == GetPawn()) + { + continue; + } + + // Check owner. + auto MyActor = GetPawn(); + auto TargetActor = HitResult.Actor.Get(); + + if (MyActor && TargetActor) + { + auto MyOwnerComponent = MyActor->FindComponentByClass(); + + if (MyOwnerComponent && MyOwnerComponent->IsSameTeamAsActor(TargetActor)) + { + continue; + } + } + + // Check if found attackable actor. + if (!URTSGameplayTagLibrary::HasGameplayTag(HitResult.Actor.Get(), URTSGameplayTagLibrary::Status_Permanent_CanBeAttacked())) + { + continue; + } + + // Acquire target. + Blackboard->SetValueAsObject(TEXT("TargetActor"), HitResult.Actor.Get()); + + UE_LOG(LogRTS, Log, TEXT("%s automatically acquired target %s."), *GetPawn()->GetName(), *HitResult.Actor->GetName()); + return; + } +} + +bool ARTSPawnAIController::HasOrder(ERTSOrderType OrderType) const +{ + return Blackboard->GetValueAsEnum(TEXT("OrderType")) == (uint8)OrderType; +} + +bool ARTSPawnAIController::IsIdle() const +{ + return HasOrder(ERTSOrderType::ORDER_None); +} + +void ARTSPawnAIController::IssueAttackOrder(AActor* Target) +{ + if (!VerifyBlackboard()) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_Attack); + ClearBuildingClass(); + ClearHomeLocation(); + SetTargetActor(Target); + ClearTargetLocation(); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::IssueBeginConstructionOrder(TSubclassOf BuildingClass, const FVector& TargetLocation) +{ + if (!VerifyBlackboard()) + { + return; + } + + // Somehow, classes are not properly serialized to blackboard values and back, so we're going to use the building index here instead. + URTSBuilderComponent* BuilderComponent = GetPawn()->FindComponentByClass(); + + if (!BuilderComponent) + { + return; + } + + int32 BuildingIndex = BuilderComponent->GetConstructibleBuildingClasses().IndexOfByKey(BuildingClass); + + if (BuildingIndex == INDEX_NONE) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_BeginConstruction); + SetBuildingClass(BuildingIndex); + ClearHomeLocation(); + ClearTargetActor(); + SetTargetLocation(TargetLocation); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::IssueContinueConstructionOrder(AActor* ConstructionSite) +{ + if (!VerifyBlackboard()) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_ContinueConstruction); + ClearBuildingClass(); + ClearHomeLocation(); + SetTargetActor(ConstructionSite); + ClearTargetLocation(); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::IssueGatherOrder(AActor* ResourceSource) +{ + if (!VerifyBlackboard()) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_Gather); + ClearBuildingClass(); + ClearHomeLocation(); + SetTargetActor(ResourceSource); + ClearTargetLocation(); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::IssueMoveOrder(const FVector& Location) +{ + if (!VerifyBlackboard()) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_Move); + ClearBuildingClass(); + ClearHomeLocation(); + ClearTargetActor(); + SetTargetLocation(Location); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::IssueReturnResourcesOrder() +{ + if (!VerifyBlackboard()) + { + return; + } + + auto GathererComponent = GetPawn()->FindComponentByClass(); + + if (!GathererComponent) + { + return; + } + + AActor* ResourceDrain = GathererComponent->FindClosestResourceDrain(); + + if (!ResourceDrain) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_ReturnResources); + ClearBuildingClass(); + ClearHomeLocation(); + SetTargetActor(ResourceDrain); + ClearTargetLocation(); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::IssueStopOrder() +{ + if (!VerifyBlackboard()) + { + return; + } + + // Update blackboard. + SetOrderType(ERTSOrderType::ORDER_None); + ClearBuildingClass(); + SetHomeLocation(GetPawn()->GetActorLocation()); + ClearTargetActor(); + ClearTargetLocation(); + + // Stop any current orders and start over. + ApplyOrders(); +} + +void ARTSPawnAIController::ApplyOrders() +{ + // Update behavior tree. + UBehaviorTreeComponent* BehaviorTreeComponent = Cast(BrainComponent); + if (BehaviorTreeComponent) + { + BehaviorTreeComponent->RestartTree(); + } + + // Notify listeners. + uint8 NewOrder = Blackboard->GetValueAsEnum(TEXT("OrderType")); + OnOrderChanged.Broadcast(GetOwner(), (ERTSOrderType)NewOrder); +} + +void ARTSPawnAIController::ClearBuildingClass() +{ + Blackboard->ClearValue(TEXT("BuildingClass")); +} + +void ARTSPawnAIController::ClearHomeLocation() +{ + Blackboard->ClearValue(TEXT("HomeLocation")); +} + +void ARTSPawnAIController::ClearTargetActor() +{ + Blackboard->ClearValue(TEXT("TargetActor")); +} + +void ARTSPawnAIController::ClearTargetLocation() +{ + Blackboard->ClearValue(TEXT("TargetLocation")); +} + +void ARTSPawnAIController::SetBuildingClass(int32 BuildingIndex) +{ + Blackboard->SetValueAsInt(TEXT("BuildingClass"), BuildingIndex); +} + +void ARTSPawnAIController::SetHomeLocation(const FVector& HomeLocation) +{ + Blackboard->SetValueAsVector(TEXT("HomeLocation"), HomeLocation); +} + +void ARTSPawnAIController::SetOrderType(const ERTSOrderType OrderType) +{ + Blackboard->SetValueAsEnum(TEXT("OrderType"), (uint8)OrderType); +} + +void ARTSPawnAIController::SetTargetActor(AActor* TargetActor) +{ + Blackboard->SetValueAsObject(TEXT("TargetActor"), TargetActor); +} + +void ARTSPawnAIController::SetTargetLocation(const FVector& TargetLocation) +{ + Blackboard->SetValueAsVector(TEXT("TargetLocation"), TargetLocation); +} + +bool ARTSPawnAIController::TraceSphere( + const FVector& Location, + const float Radius, + AActor* ActorToIgnore, + ECollisionChannel TraceChannel, + TArray& OutHitResults) +{ + UWorld* World = GetWorld(); + + if (!World) + { + return false; + } + + const FVector Start = Location; + const FVector End = Location + FVector::ForwardVector * Radius; + + return World->SweepMultiByObjectType( + OutHitResults, + Start, + End, + FQuat(), + FCollisionObjectQueryParams(TraceChannel), + FCollisionShape::MakeSphere(Radius) + ); +} + +bool ARTSPawnAIController::VerifyBlackboard() +{ + if (!Blackboard) + { + UE_LOG(LogRTS, Warning, TEXT("Blackboard not set up for %s, can't receive orders. Check AI Controller Class and Auto Possess AI."), *GetPawn()->GetName()); + return false; + } + + return true; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPawnMovementComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPawnMovementComponent.cpp new file mode 100644 index 00000000..1421894f --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPawnMovementComponent.cpp @@ -0,0 +1,19 @@ +#include "RTSPawnMovementComponent.h" + + +URTSPawnMovementComponent::URTSPawnMovementComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + bUpdateRotation = true; +} + +void URTSPawnMovementComponent::UpdateComponentVelocity() +{ + Super::UpdateComponentVelocity(); + + if (bUpdateRotation && !Velocity.IsNearlyZero()) + { + MoveUpdatedComponent(FVector::ZeroVector, Velocity.Rotation(), false); + } +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerAIController.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerAIController.cpp new file mode 100644 index 00000000..f4a0c0e2 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerAIController.cpp @@ -0,0 +1,298 @@ +#include "RTSPlayerAIController.h" + +#include "EngineUtils.h" +#include "BehaviorTree/BehaviorTreeComponent.h" +#include "BehaviorTree/BlackboardComponent.h" +#include "GameFramework/Pawn.h" + +#include "RTSPawnAIController.h" +#include "RTSLog.h" +#include "Construction/RTSBuilderComponent.h" +#include "Construction/RTSConstructionSiteComponent.h" +#include "Economy/RTSPlayerResourcesComponent.h" +#include "Economy/RTSResourceSourceComponent.h" +#include "Economy/RTSResourceDrainComponent.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Libraries/RTSGameplayLibrary.h" +#include "Production/RTSProductionComponent.h" +#include "Production/RTSProductionCostComponent.h" + + +ARTSPlayerAIController::ARTSPlayerAIController(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PlayerResourcesComponent = CreateDefaultSubobject(TEXT("Player Resources")); + + bWantsPlayerState = true; + + // Set reasonable default values. + MaximumBaseBuildingDistance = 1500.0f; +} + +TSubclassOf ARTSPlayerAIController::GetNextPawnToProduce() const +{ + // Count own actors. + TMap, int32> OwnPawns; + + for (TActorIterator PawnItr(GetWorld()); PawnItr; ++PawnItr) + { + APawn* SomePawn = *PawnItr; + + if (SomePawn->GetOwner() != this) + { + continue; + } + + int32& NumOwnedPawns = OwnPawns.FindOrAdd(SomePawn->GetClass()); + ++NumOwnedPawns; + } + + // TODO(np): Also count actors already in production/construction. + + // Check build order. + TMap, int32> BuildOrderPawns; + for (TSubclassOf PawnClass : BuildOrder) + { + int32& NumRequiredPawns = BuildOrderPawns.FindOrAdd(PawnClass); + ++NumRequiredPawns; + + if (NumRequiredPawns > OwnPawns.FindRef(PawnClass)) + { + return PawnClass; + } + } + + return APawn::StaticClass(); +} + +AActor* ARTSPlayerAIController::GetPrimaryResourceDrain() const +{ + APawn* PrimaryResourceDrain = nullptr; + + for (TActorIterator PawnItr(GetWorld()); PawnItr; ++PawnItr) + { + APawn* SomePawn = *PawnItr; + + if (SomePawn->GetOwner() != this) + { + continue; + } + + if (SomePawn->FindComponentByClass() == nullptr) + { + continue; + } + + return SomePawn; + } + + return nullptr; +} + +AActor* ARTSPlayerAIController::GetPrimaryResourceSource() const +{ + // Get resource drain. + AActor* PrimaryResourceDrain = GetPrimaryResourceDrain(); + + if (PrimaryResourceDrain == nullptr) + { + return nullptr; + } + + // Sweep for sources. + AActor* ClosestResourceSource = nullptr; + float ClosestResourceSourceDistance = 0.0f; + + for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) + { + auto ResourceSource = *ActorItr; + + // Check if found resource source. + auto ResourceSourceComponent = ResourceSource->FindComponentByClass(); + + if (!ResourceSourceComponent) + { + continue; + } + + // Check resource type. + if (ResourceSourceComponent->GetResourceType() != PrimaryResourceType) + { + continue; + } + + // Check distance. + float Distance = PrimaryResourceDrain->GetDistanceTo(ResourceSource); + + if (!ClosestResourceSource || Distance < ClosestResourceSourceDistance) + { + ClosestResourceSource = ResourceSource; + ClosestResourceSourceDistance = Distance; + } + } + + return ClosestResourceSource; +} + +bool ARTSPlayerAIController::CanPayFor(TSubclassOf PawnClass) const +{ + URTSProductionCostComponent* ProductionCostComponent = URTSGameplayLibrary::FindDefaultComponentByClass(PawnClass); + + if (ProductionCostComponent) + { + return PlayerResourcesComponent->CanPayAllResources(ProductionCostComponent->GetResources()); + } + + URTSConstructionSiteComponent* ConstructionSiteComponent = URTSGameplayLibrary::FindDefaultComponentByClass(PawnClass); + + if (ConstructionSiteComponent) + { + return PlayerResourcesComponent->CanPayAllResources(ConstructionSiteComponent->GetConstructionCosts()); + } + + return true; +} + +bool ARTSPlayerAIController::MeetsAllRequirementsFor(TSubclassOf PawnClass) const +{ + AActor* AnyOwnActor = GetPrimaryResourceDrain(); + return URTSGameplayLibrary::OwnerMeetsAllRequirementsFor(AnyOwnActor, AnyOwnActor, PawnClass); +} + +bool ARTSPlayerAIController::StartProduction(TSubclassOf PawnClass) +{ + // Find suitable factory. + for (TActorIterator PawnItr(GetWorld()); PawnItr; ++PawnItr) + { + APawn* SomePawn = *PawnItr; + + if (SomePawn->GetOwner() != this) + { + continue; + } + + URTSProductionComponent* ProductionComponent = SomePawn->FindComponentByClass(); + + if (!ProductionComponent) + { + continue; + } + + if (!ProductionComponent->GetAvailableProducts().Contains(PawnClass)) + { + continue; + } + + // Start production. + ProductionComponent->StartProduction(PawnClass); + return true; + } + + // Get any own building location. + APawn* OwnBuilding = nullptr; + + for (TActorIterator PawnItr(GetWorld()); PawnItr; ++PawnItr) + { + APawn* SomePawn = *PawnItr; + + if (SomePawn->GetOwner() != this) + { + continue; + } + + URTSConstructionSiteComponent* ConstructionSiteComponent = SomePawn->FindComponentByClass(); + + if (!ConstructionSiteComponent) + { + continue; + } + + OwnBuilding = SomePawn; + break; + } + + // Find suitable builder. + UWorld* World = GetWorld(); + + if (!World) + { + return false; + } + + for (TActorIterator PawnItr(GetWorld()); PawnItr; ++PawnItr) + { + APawn* SomePawn = *PawnItr; + + if (SomePawn->GetOwner() != this) + { + continue; + } + + URTSBuilderComponent* BuilderComponent = SomePawn->FindComponentByClass(); + + if (!BuilderComponent) + { + continue; + } + + if (!BuilderComponent->GetConstructibleBuildingClasses().Contains(PawnClass)) + { + continue; + } + + ARTSPawnAIController* PawnController = Cast(SomePawn->GetController()); + + if (!PawnController) + { + continue; + } + + if (PawnController->HasOrder(ERTSOrderType::ORDER_BeginConstruction) || + PawnController->HasOrder(ERTSOrderType::ORDER_ContinueConstruction)) + { + // Don't take builders away from constructing other buildings. + continue; + } + + // Find suitable building location: Get random nearby location. + FVector TargetLocation = OwnBuilding != nullptr ? OwnBuilding->GetActorLocation() : SomePawn->GetActorLocation(); + TargetLocation.X += FMath::FRandRange(-MaximumBaseBuildingDistance, MaximumBaseBuildingDistance); + TargetLocation.Y += FMath::FRandRange(-MaximumBaseBuildingDistance, MaximumBaseBuildingDistance); + + TargetLocation = URTSCollisionLibrary::GetGroundLocation(this, TargetLocation); + + // If there's a primary resource drain, prevent blocking its path. + AActor* PrimaryResourceSource = GetPrimaryResourceSource(); + AActor* PrimaryResourceDrain = GetPrimaryResourceDrain(); + + if (PrimaryResourceSource != nullptr && + PrimaryResourceDrain != nullptr && + FVector::DistSquaredXY(PrimaryResourceSource->GetActorLocation(), TargetLocation) < + FVector::DistSquaredXY(PrimaryResourceSource->GetActorLocation(), PrimaryResourceDrain->GetActorLocation())) + { + continue; + } + + // Issue construction order. + PawnController->IssueBeginConstructionOrder(PawnClass, TargetLocation); + return true; + } + + return false; +} + +void ARTSPlayerAIController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + // Make AI use assigned blackboard. + UBlackboardComponent* BlackboardComponent; + + if (!UseBlackboard(PlayerBlackboardAsset, BlackboardComponent)) + { + UE_LOG(LogRTS, Warning, TEXT("Failed to set up blackboard for AI %s."), *GetName()); + } + + // Run behavior tree. + RunBehaviorTree(PlayerBehaviorTreeAsset); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerAdvantageComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerAdvantageComponent.cpp new file mode 100644 index 00000000..f7a9c079 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerAdvantageComponent.cpp @@ -0,0 +1,28 @@ +#include "RTSPlayerAdvantageComponent.h" + +URTSPlayerAdvantageComponent::URTSPlayerAdvantageComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + bGodModeEnabled = false; + SpeedBoostFactor = 1.0f; +} + +bool URTSPlayerAdvantageComponent::IsGodModeEnabled() const +{ + return bGodModeEnabled; +} + +float URTSPlayerAdvantageComponent::GetSpeedBoostFactor() const +{ + return SpeedBoostFactor; +} + +void URTSPlayerAdvantageComponent::SetGodModeEnabled(bool bInGodModeEnabled) +{ + bGodModeEnabled = bInGodModeEnabled; +} + +void URTSPlayerAdvantageComponent::SetSpeedBoostFactor(float InSpeedBoostFactor) +{ + SpeedBoostFactor = InSpeedBoostFactor; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerController.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerController.cpp new file mode 100644 index 00000000..d5a1d3b4 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerController.cpp @@ -0,0 +1,1703 @@ +#include "RTSPlayerController.h" + +#include "EngineUtils.h" +#include "Landscape.h" +#include "Camera/CameraComponent.h" +#include "Components/InputComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/Engine.h" +#include "Engine/LocalPlayer.h" +#include "Engine/SkeletalMesh.h" +#include "Kismet/GameplayStatics.h" +#include "Sound/SoundCue.h" + +#include "RTSCameraBoundsVolume.h" +#include "RTSPawnAIController.h" +#include "RTSGameMode.h" +#include "RTSLog.h" +#include "RTSNameComponent.h" +#include "RTSOwnerComponent.h" +#include "RTSPlayerAdvantageComponent.h" +#include "RTSPlayerState.h" +#include "RTSSelectableComponent.h" +#include "RTSTeamInfo.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Libraries/RTSGameplayLibrary.h" +#include "Libraries/RTSGameplayTagLibrary.h" +#include "Combat/RTSAttackComponent.h" +#include "Construction/RTSBuilderComponent.h" +#include "Construction/RTSBuildingCursor.h" +#include "Construction/RTSConstructionSiteComponent.h" +#include "Economy/RTSGathererComponent.h" +#include "Economy/RTSPlayerResourcesComponent.h" +#include "Economy/RTSResourceSourceComponent.h" +#include "Production/RTSProductionComponent.h" +#include "Production/RTSProductionCostComponent.h" +#include "Vision/RTSFogOfWarActor.h" +#include "Vision/RTSVisionInfo.h" + + +ARTSPlayerController::ARTSPlayerController(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PlayerAdvantageComponent = CreateDefaultSubobject(TEXT("Player Advantage")); + PlayerResourcesComponent = CreateDefaultSubobject(TEXT("Player Resources")); + + // Set reasonable default values. + CameraSpeed = 1000.0f; + CameraZoomSpeed = 4000.0f; + + MinCameraDistance = 500.0f; + MaxCameraDistance = 2500.0f; + + CameraScrollThreshold = 20.0f; +} + +void ARTSPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + // Allow immediate updates for interested listeners. + for (int32 Index = 0; Index < PlayerResourcesComponent->GetResourceTypes().Num(); ++Index) + { + PlayerResourcesComponent->OnResourcesChanged.Broadcast( + PlayerResourcesComponent->GetResourceTypes()[Index], + 0.0f, + PlayerResourcesComponent->GetResources(PlayerResourcesComponent->GetResourceTypes()[Index]), + true); + } +} + +void ARTSPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + + // Enable mouse input. + APlayerController::bShowMouseCursor = true; + APlayerController::bEnableClickEvents = true; + APlayerController::bEnableMouseOverEvents = true; + + // Bind actions. + InputComponent->BindAction(TEXT("Select"), IE_Pressed, this, &ARTSPlayerController::StartSelectActors); + InputComponent->BindAction(TEXT("Select"), IE_Released, this, &ARTSPlayerController::FinishSelectActors); + InputComponent->BindAction(TEXT("AddSelection"), IE_Pressed, this, &ARTSPlayerController::StartAddSelection); + InputComponent->BindAction(TEXT("AddSelection"), IE_Released, this, &ARTSPlayerController::StopAddSelection); + InputComponent->BindAction(TEXT("ToggleSelection"), IE_Pressed, this, &ARTSPlayerController::StartToggleSelection); + InputComponent->BindAction(TEXT("ToggleSelection"), IE_Released, this, &ARTSPlayerController::StopToggleSelection); + + InputComponent->BindAction(TEXT("IssueOrder"), IE_Released, this, &ARTSPlayerController::IssueOrder); + InputComponent->BindAction(TEXT("IssueStopOrder"), IE_Released, this, &ARTSPlayerController::IssueStopOrder); + + InputComponent->BindAction(TEXT("SaveControlGroup0"), IE_Released, this, &ARTSPlayerController::SaveControlGroup0); + InputComponent->BindAction(TEXT("SaveControlGroup1"), IE_Released, this, &ARTSPlayerController::SaveControlGroup1); + InputComponent->BindAction(TEXT("SaveControlGroup2"), IE_Released, this, &ARTSPlayerController::SaveControlGroup2); + InputComponent->BindAction(TEXT("SaveControlGroup3"), IE_Released, this, &ARTSPlayerController::SaveControlGroup3); + InputComponent->BindAction(TEXT("SaveControlGroup4"), IE_Released, this, &ARTSPlayerController::SaveControlGroup4); + InputComponent->BindAction(TEXT("SaveControlGroup5"), IE_Released, this, &ARTSPlayerController::SaveControlGroup5); + InputComponent->BindAction(TEXT("SaveControlGroup6"), IE_Released, this, &ARTSPlayerController::SaveControlGroup6); + InputComponent->BindAction(TEXT("SaveControlGroup7"), IE_Released, this, &ARTSPlayerController::SaveControlGroup7); + InputComponent->BindAction(TEXT("SaveControlGroup8"), IE_Released, this, &ARTSPlayerController::SaveControlGroup8); + InputComponent->BindAction(TEXT("SaveControlGroup9"), IE_Released, this, &ARTSPlayerController::SaveControlGroup9); + + InputComponent->BindAction(TEXT("LoadControlGroup0"), IE_Released, this, &ARTSPlayerController::LoadControlGroup0); + InputComponent->BindAction(TEXT("LoadControlGroup1"), IE_Released, this, &ARTSPlayerController::LoadControlGroup1); + InputComponent->BindAction(TEXT("LoadControlGroup2"), IE_Released, this, &ARTSPlayerController::LoadControlGroup2); + InputComponent->BindAction(TEXT("LoadControlGroup3"), IE_Released, this, &ARTSPlayerController::LoadControlGroup3); + InputComponent->BindAction(TEXT("LoadControlGroup4"), IE_Released, this, &ARTSPlayerController::LoadControlGroup4); + InputComponent->BindAction(TEXT("LoadControlGroup5"), IE_Released, this, &ARTSPlayerController::LoadControlGroup5); + InputComponent->BindAction(TEXT("LoadControlGroup6"), IE_Released, this, &ARTSPlayerController::LoadControlGroup6); + InputComponent->BindAction(TEXT("LoadControlGroup7"), IE_Released, this, &ARTSPlayerController::LoadControlGroup7); + InputComponent->BindAction(TEXT("LoadControlGroup8"), IE_Released, this, &ARTSPlayerController::LoadControlGroup8); + InputComponent->BindAction(TEXT("LoadControlGroup9"), IE_Released, this, &ARTSPlayerController::LoadControlGroup9); + + InputComponent->BindAxis(TEXT("MoveCameraLeftRight"), this, &ARTSPlayerController::MoveCameraLeftRight); + InputComponent->BindAxis(TEXT("MoveCameraUpDown"), this, &ARTSPlayerController::MoveCameraUpDown); + InputComponent->BindAxis(TEXT("ZoomCamera"), this, &ARTSPlayerController::ZoomCamera); + + InputComponent->BindAction(TEXT("ShowConstructionProgressBars"), IE_Pressed, this, &ARTSPlayerController::StartShowingConstructionProgressBars); + InputComponent->BindAction(TEXT("ShowConstructionProgressBars"), IE_Released, this, &ARTSPlayerController::StopShowingConstructionProgressBars); + InputComponent->BindAction(TEXT("ShowHealthBars"), IE_Pressed, this, &ARTSPlayerController::StartShowingHealthBars); + InputComponent->BindAction(TEXT("ShowHealthBars"), IE_Released, this, &ARTSPlayerController::StopShowingHealthBars); + InputComponent->BindAction(TEXT("ShowProductionProgressBars"), IE_Pressed, this, &ARTSPlayerController::StartShowingProductionProgressBars); + InputComponent->BindAction(TEXT("ShowProductionProgressBars"), IE_Released, this, &ARTSPlayerController::StopShowingProductionProgressBars); + + InputComponent->BindAction(TEXT("ConfirmBuildingPlacement"), IE_Released, this, &ARTSPlayerController::ConfirmBuildingPlacement); + InputComponent->BindAction(TEXT("CancelBuildingPlacement"), IE_Released, this, &ARTSPlayerController::CancelBuildingPlacement); + + InputComponent->BindAction(TEXT("CancelConstruction"), IE_Released, this, &ARTSPlayerController::CancelConstruction); + InputComponent->BindAction(TEXT("CancelProduction"), IE_Released, this, &ARTSPlayerController::CancelProduction); + + // Get camera bounds. + for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) + { + CameraBoundsVolume = *ActorItr; + break; + } + + if (!CameraBoundsVolume) + { + UE_LOG(LogRTS, Warning, TEXT("No RTSCameraBoundsVolume found. Camera will be able to move anywhere.")); + } + + // Setup control groups. + ControlGroups.SetNum(10); +} + +void ARTSPlayerController::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); +} + +AActor* ARTSPlayerController::GetHoveredActor() const +{ + return HoveredActor; +} + +ARTSPlayerState* ARTSPlayerController::GetPlayerState() const +{ + return Cast(PlayerState); +} + +TArray ARTSPlayerController::GetSelectedActors() const +{ + return SelectedActors; +} + +bool ARTSPlayerController::GetObjectsAtScreenPosition(FVector2D ScreenPosition, TArray& OutHitResults) const +{ + // Get ray. + FVector WorldOrigin; + FVector WorldDirection; + if (!UGameplayStatics::DeprojectScreenToWorld(this, ScreenPosition, WorldOrigin, WorldDirection)) + { + return false; + } + + // Cast ray. + return TraceObjects(WorldOrigin, WorldDirection, OutHitResults); +} + +bool ARTSPlayerController::GetObjectsAtWorldPosition(const FVector& WorldPositionXY, TArray& OutHitResults) const +{ + // Get ray. + FVector WorldOrigin = FVector(WorldPositionXY.X, WorldPositionXY.Y, HitResultTraceDistance / 2); + FVector WorldDirection = -FVector::UpVector; + + // Cast ray. + return TraceObjects(WorldOrigin, WorldDirection, OutHitResults); +} + +bool ARTSPlayerController::GetSelectionFrame(FIntRect& OutSelectionFrame) const +{ + if (!bCreatingSelectionFrame) + { + return false; + } + + // Get mouse input. + float MouseX; + float MouseY; + + if (!GetMousePosition(MouseX, MouseY)) + { + return false; + } + + float MinX = FMath::Min(SelectionFrameMouseStartPosition.X, MouseX); + float MaxX = FMath::Max(SelectionFrameMouseStartPosition.X, MouseX); + float MinY = FMath::Min(SelectionFrameMouseStartPosition.Y, MouseY); + float MaxY = FMath::Max(SelectionFrameMouseStartPosition.Y, MouseY); + + OutSelectionFrame = FIntRect(FIntPoint(MinX, MinY), FIntPoint(MaxX, MaxY)); + + return true; +} + +ARTSTeamInfo* ARTSPlayerController::GetTeamInfo() const +{ + ARTSPlayerState* CurrentPlayerState = Cast(PlayerState); + + if (CurrentPlayerState) + { + return CurrentPlayerState->GetTeam(); + } + + return nullptr; +} + +bool ARTSPlayerController::GetObjectsAtPointerPosition(TArray& OutHitResults) const +{ + // Get local player viewport. + ULocalPlayer* LocalPlayer = Cast(Player); + + if (!LocalPlayer || !LocalPlayer->ViewportClient) + { + return false; + } + + // Get mouse position. + FVector2D MousePosition; + if (!LocalPlayer->ViewportClient->GetMousePosition(MousePosition)) + { + return false; + } + + return GetObjectsAtScreenPosition(MousePosition, OutHitResults); +} + +bool ARTSPlayerController::GetObjectsInSelectionFrame(TArray& HitResults) const +{ + UWorld* World = GetWorld(); + + if (!World) + { + return false; + } + + // Get selection frame. + FIntRect SelectionFrame; + + if (!GetSelectionFrame(SelectionFrame)) + { + return false; + } + + if (SelectionFrame.Area() < 10) + { + // Selection frame too small - just consider left-click. + return GetObjectsAtPointerPosition(HitResults); + } + + // Iterate all actors. + HitResults.Reset(); + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + + FVector2D ActorScreenPosition; + + if (UGameplayStatics::ProjectWorldToScreen(this, Actor->GetActorLocation(), ActorScreenPosition)) + { + if (SelectionFrame.Contains(FIntPoint(ActorScreenPosition.X, ActorScreenPosition.Y))) + { + FHitResult HitResult(Actor, nullptr, Actor->GetActorLocation(), FVector()); + HitResults.Add(HitResult); + } + } + } + + return HitResults.Num() > 0; +} + +bool ARTSPlayerController::TraceObjects(const FVector& WorldOrigin, const FVector& WorldDirection, TArray& OutHitResults) const +{ + UWorld* World = GetWorld(); + + if (!World) + { + return false; + } + + FCollisionObjectQueryParams Params(FCollisionObjectQueryParams::InitType::AllObjects); + + return World->LineTraceMultiByObjectType( + OutHitResults, + WorldOrigin, + WorldOrigin + WorldDirection * HitResultTraceDistance, + Params); +} + +bool ARTSPlayerController::IsSelectableActor(AActor* Actor) const +{ + // Check if valid. + if (!IsValid(Actor)) + { + return false; + } + + // Check if selectable. + auto SelectableComponent = Actor->FindComponentByClass(); + + if (!SelectableComponent) + { + return false; + } + + return true; +} + +void ARTSPlayerController::IssueOrder() +{ + // Get objects at pointer position. + TArray HitResults; + + if (!GetObjectsAtPointerPosition(HitResults)) + { + return; + } + + IssueOrderTargetingObjects(HitResults); +} + +void ARTSPlayerController::IssueOrderTargetingObjects(TArray& HitResults) +{ + // Check if there's anybody to receive the order. + if (SelectedActors.Num() == 0) + { + return; + } + + // Get target location. + TOptional TargetLocation; + + for (auto& HitResult : HitResults) + { + TargetLocation = HitResult.Location; + + if (HitResult.Actor != nullptr) + { + // Issue attack order. + if (IssueAttackOrder(HitResult.Actor.Get())) + { + return; + } + + // Issue gather order. + if (IssueGatherOrder(HitResult.Actor.Get())) + { + return; + } + + // Issue construct order. + if (IssueContinueConstructionOrder(HitResult.Actor.Get())) + { + return; + } + + ALandscape* Landscape = Cast(HitResult.Actor.Get()); + + if (Landscape != nullptr) + { + // Issue move order. + IssueMoveOrder(HitResult.Location); + return; + } + } + } + + if (TargetLocation.IsSet()) + { + // Issue move order. + IssueMoveOrder(TargetLocation.GetValue()); + } +} + +bool ARTSPlayerController::IssueAttackOrder(AActor* Target) +{ + if (!Target) + { + return false; + } + + if (!URTSGameplayTagLibrary::HasGameplayTag(Target, URTSGameplayTagLibrary::Status_Permanent_CanBeAttacked())) + { + return false; + } + + ARTSTeamInfo* MyTeam = GetPlayerState()->GetTeam(); + + // Issue attack orders. + bool bSuccess = false; + + for (auto SelectedActor : SelectedActors) + { + APawn* SelectedPawn = Cast(SelectedActor); + + if (!SelectedPawn) + { + continue; + } + + if (SelectedPawn->GetOwner() != this) + { + continue; + } + + // Verify target. + auto TargetOwnerComponent = Target->FindComponentByClass(); + + if (TargetOwnerComponent && TargetOwnerComponent->IsSameTeamAsActor(SelectedActor)) + { + continue; + } + + if (SelectedActor->FindComponentByClass() == nullptr) + { + continue; + } + + // Send attack order to server. + ServerIssueAttackOrder(SelectedPawn, Target); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to attack %s."), *SelectedActor->GetName(), *Target->GetName()); + + // Notify listeners. + NotifyOnIssuedAttackOrder(SelectedPawn, Target); + } + + bSuccess = true; + } + + return bSuccess; +} + +void ARTSPlayerController::ServerIssueAttackOrder_Implementation(APawn* OrderedPawn, AActor* Target) +{ + auto PawnController = Cast(OrderedPawn->GetController()); + + if (!PawnController) + { + return; + } + + // Issue attack order. + PawnController->IssueAttackOrder(Target); + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to attack %s."), *OrderedPawn->GetName(), *Target->GetName()); + + // Notify listeners. + NotifyOnIssuedAttackOrder(OrderedPawn, Target); +} + +bool ARTSPlayerController::ServerIssueAttackOrder_Validate(APawn* OrderedPawn, AActor* Target) +{ + // Verify owner to prevent cheating. + return OrderedPawn->GetOwner() == this; +} + +bool ARTSPlayerController::IssueBeginConstructionOrder(TSubclassOf BuildingClass, const FVector& TargetLocation) +{ + // Find suitable selected builder. + for (auto SelectedActor : SelectedActors) + { + APawn* SelectedPawn = Cast(SelectedActor); + + if (!SelectedPawn) + { + continue; + } + + if (SelectedPawn->GetOwner() != this) + { + continue; + } + + // Check if builder. + auto BuilderComponent = SelectedPawn->FindComponentByClass(); + + if (!BuilderComponent) + { + continue; + } + + // Check if builder knows about building. + if (!BuilderComponent->GetConstructibleBuildingClasses().Contains(BuildingClass)) + { + continue; + } + + // Send construction order to server. + ServerIssueBeginConstructionOrder(SelectedPawn, BuildingClass, TargetLocation); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to begin constructing %s at %s."), *SelectedPawn->GetName(), *BuildingClass->GetName(), *TargetLocation.ToString()); + + // Notify listeners. + NotifyOnIssuedBeginConstructionOrder(SelectedPawn, BuildingClass, TargetLocation); + } + + // Just send one builder. + return true; + } + + return false; +} + +bool ARTSPlayerController::ServerIssueContinueConstructionOrder_Validate(APawn* OrderedPawn, AActor* ConstructionSite) +{ + // Verify owner to prevent cheating. + return OrderedPawn->GetOwner() == this; +} + +void ARTSPlayerController::ServerIssueBeginConstructionOrder_Implementation(APawn* OrderedPawn, TSubclassOf BuildingClass, const FVector& TargetLocation) +{ + auto PawnController = Cast(OrderedPawn->GetController()); + + if (!PawnController) + { + return; + } + + // Issue construction order. + PawnController->IssueBeginConstructionOrder(BuildingClass, TargetLocation); + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to begin constructing %s at %s."), *OrderedPawn->GetName(), *BuildingClass->GetName(), *TargetLocation.ToString()); + + // Notify listeners. + NotifyOnIssuedBeginConstructionOrder(OrderedPawn, BuildingClass, TargetLocation); +} + +bool ARTSPlayerController::IssueContinueConstructionOrder(AActor* ConstructionSite) +{ + if (!ConstructionSite) + { + return false; + } + + auto ConstructionSiteComponent = ConstructionSite->FindComponentByClass(); + + if (!ConstructionSiteComponent || ConstructionSiteComponent->IsFinished()) + { + return false; + } + + ARTSTeamInfo* MyTeam = GetPlayerState()->GetTeam(); + + // Issue construction orders. + bool bSuccess = false; + + for (auto SelectedActor : SelectedActors) + { + APawn* SelectedPawn = Cast(SelectedActor); + + if (!SelectedPawn) + { + continue; + } + + if (SelectedPawn->GetOwner() != this) + { + continue; + } + + // Verify target. + auto TargetOwnerComponent = ConstructionSite->FindComponentByClass(); + + if (TargetOwnerComponent && !TargetOwnerComponent->IsSameTeamAsActor(SelectedActor)) + { + continue; + } + + if (SelectedActor->FindComponentByClass() == nullptr) + { + continue; + } + + // Send construction order to server. + ServerIssueContinueConstructionOrder(SelectedPawn, ConstructionSite); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to continue constructing %s."), *SelectedActor->GetName(), *ConstructionSite->GetName()); + + // Notify listeners. + NotifyOnIssuedContinueConstructionOrder(SelectedPawn, ConstructionSite); + } + + bSuccess = true; + } + + return bSuccess; +} + +bool ARTSPlayerController::IssueGatherOrder(AActor* ResourceSource) +{ + if (!ResourceSource) + { + return false; + } + + auto ResourceSourceComponent = ResourceSource->FindComponentByClass(); + + if (!ResourceSourceComponent) + { + return false; + } + + // Issue gather orders. + bool bSuccess = false; + + for (auto SelectedActor : SelectedActors) + { + APawn* SelectedPawn = Cast(SelectedActor); + + if (!SelectedPawn) + { + continue; + } + + if (SelectedPawn->GetOwner() != this) + { + continue; + } + + // Verify gatherer. + auto GathererComponent = SelectedActor->FindComponentByClass(); + if (!GathererComponent || !GathererComponent->CanGatherFrom(ResourceSource)) + { + continue; + } + + // Send gather order to server. + ServerIssueGatherOrder(SelectedPawn, ResourceSource); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to gather resources from %s."), *SelectedActor->GetName(), *ResourceSource->GetName()); + + // Notify listeners. + NotifyOnIssuedGatherOrder(SelectedPawn, ResourceSource); + } + + bSuccess = true; + } + + return bSuccess; +} + +void ARTSPlayerController::ServerIssueGatherOrder_Implementation(APawn* OrderedPawn, AActor* ResourceSource) +{ + auto PawnController = Cast(OrderedPawn->GetController()); + + if (!PawnController) + { + return; + } + + // Issue gather order. + PawnController->IssueGatherOrder(ResourceSource); + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to gather resources from %s."), *OrderedPawn->GetName(), *ResourceSource->GetName()); + + // Notify listeners. + NotifyOnIssuedGatherOrder(OrderedPawn, ResourceSource); +} + +bool ARTSPlayerController::ServerIssueGatherOrder_Validate(APawn* OrderedPawn, AActor* ResourceSourc) +{ + // Verify owner to prevent cheating. + return OrderedPawn->GetOwner() == this; +} + +void ARTSPlayerController::ServerIssueContinueConstructionOrder_Implementation(APawn* OrderedPawn, AActor* ConstructionSite) +{ + auto PawnController = Cast(OrderedPawn->GetController()); + + if (!PawnController) + { + return; + } + + // Issue construction order. + PawnController->IssueContinueConstructionOrder(ConstructionSite); + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to continue constructing %s."), *OrderedPawn->GetName(), *ConstructionSite->GetName()); + + // Notify listeners. + NotifyOnIssuedContinueConstructionOrder(OrderedPawn, ConstructionSite); +} + +bool ARTSPlayerController::ServerIssueBeginConstructionOrder_Validate(APawn* OrderedPawn, TSubclassOf BuildingClass, const FVector& TargetLocation) +{ + // Verify owner to prevent cheating. + return OrderedPawn->GetOwner() == this; +} + +bool ARTSPlayerController::IssueMoveOrder(const FVector& TargetLocation) +{ + // Issue move orders. + bool bSuccess = false; + + for (auto SelectedActor : SelectedActors) + { + // Verify pawn and owner. + auto SelectedPawn = Cast(SelectedActor); + + if (!SelectedPawn) + { + continue; + } + + if (SelectedPawn->GetOwner() != this) + { + continue; + } + + // Send move order to server. + ServerIssueMoveOrder(SelectedPawn, TargetLocation); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to move to %s."), *SelectedActor->GetName(), *TargetLocation.ToString()); + + // Notify listeners. + NotifyOnIssuedMoveOrder(SelectedPawn, TargetLocation); + } + + bSuccess = true; + } + + return bSuccess; +} + +void ARTSPlayerController::ServerIssueMoveOrder_Implementation(APawn* OrderedPawn, const FVector& TargetLocation) +{ + auto PawnController = Cast(OrderedPawn->GetController()); + + if (!PawnController) + { + return; + } + + // Issue move order. + PawnController->IssueMoveOrder(TargetLocation); + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to move to %s."), *OrderedPawn->GetName(), *TargetLocation.ToString()); + + // Notify listeners. + NotifyOnIssuedMoveOrder(OrderedPawn, TargetLocation); +} + +bool ARTSPlayerController::ServerIssueMoveOrder_Validate(APawn* OrderedPawn, const FVector& TargetLocation) +{ + // Verify owner to prevent cheating. + return OrderedPawn->GetOwner() == this; +} + +AActor* ARTSPlayerController::GetSelectedProductionActorFor(TSubclassOf ProductClass) const +{ + // Find suitable selected actor. + for (auto SelectedActor : SelectedActors) + { + if (!IsValid(SelectedActor)) + { + continue; + } + + // Verify owner. + if (SelectedActor->GetOwner() != this) + { + continue; + } + + // Check if production actor. + auto ProductionComponent = SelectedActor->FindComponentByClass(); + + if (!ProductionComponent) + { + continue; + } + + if (!ProductionComponent->GetAvailableProducts().Contains(ProductClass)) + { + continue; + } + + return SelectedActor; + } + + return nullptr; +} + +bool ARTSPlayerController::CheckCanIssueProductionOrder(TSubclassOf ProductClass) +{ + AActor* SelectedActor = GetSelectedProductionActorFor(ProductClass); + + if (!IsValid(SelectedActor)) + { + return true; + } + + // Check costs. + URTSProductionCostComponent* ProductionCostComponent = URTSGameplayLibrary::FindDefaultComponentByClass(ProductClass); + + if (ProductionCostComponent && !PlayerResourcesComponent->CanPayAllResources(ProductionCostComponent->GetResources())) + { + NotifyOnErrorOccurred(TEXT("Not enough resources.")); + return false; + } + + // Check requirements. + TSubclassOf MissingRequirement; + + if (URTSGameplayLibrary::GetMissingRequirementFor(this, SelectedActor, ProductClass, MissingRequirement)) + { + URTSNameComponent* NameComponent = URTSGameplayLibrary::FindDefaultComponentByClass(MissingRequirement); + + if (NameComponent) + { + FString ErrorMessage = TEXT("Missing requirement: "); + ErrorMessage.Append(NameComponent->GetName().ToString()); + NotifyOnErrorOccurred(ErrorMessage); + } + else + { + NotifyOnErrorOccurred("Missing requirement."); + } + + return false; + } + + return true; +} + +void ARTSPlayerController::IssueProductionOrder(TSubclassOf ProductClass) +{ + AActor* SelectedActor = GetSelectedProductionActorFor(ProductClass); + + if (!IsValid(SelectedActor)) + { + return; + } + + // Begin production. + ServerStartProduction(SelectedActor, ProductClass); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to start production %s."), *SelectedActor->GetName(), *ProductClass->GetName()); + + // Notify listeners. + NotifyOnIssuedProductionOrder(SelectedActor, ProductClass); + } +} + +void ARTSPlayerController::IssueStopOrder() +{ + // Issue stop orders. + for (auto SelectedActor : SelectedActors) + { + // Verify pawn and owner. + auto SelectedPawn = Cast(SelectedActor); + + if (!SelectedPawn) + { + continue; + } + + if (SelectedPawn->GetOwner() != this) + { + continue; + } + + // Send stop order to server. + ServerIssueStopOrder(SelectedPawn); + + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to stop."), *SelectedActor->GetName()); + + // Notify listeners. + NotifyOnIssuedStopOrder(SelectedPawn); + } + } +} + +void ARTSPlayerController::ServerIssueStopOrder_Implementation(APawn* OrderedPawn) +{ + auto PawnController = Cast(OrderedPawn->GetController()); + + if (!PawnController) + { + return; + } + + // Issue stop order. + PawnController->IssueStopOrder(); + UE_LOG(LogRTS, Log, TEXT("Ordered actor %s to stop."), *OrderedPawn->GetName()); + + // Notify listeners. + NotifyOnIssuedStopOrder(OrderedPawn); +} + +bool ARTSPlayerController::ServerIssueStopOrder_Validate(APawn* OrderedPawn) +{ + // Verify owner to prevent cheating. + return OrderedPawn->GetOwner() == this; +} + +void ARTSPlayerController::SelectActors(TArray Actors) +{ + // Clear selection. + for (AActor* SelectedActor : SelectedActors) + { + URTSSelectableComponent* SelectableComponent = SelectedActor->FindComponentByClass(); + + if (SelectableComponent) + { + SelectableComponent->DeselectActor(); + } + } + + // Apply new selection. + SelectedActors = Actors; + + for (AActor* SelectedActor : SelectedActors) + { + URTSSelectableComponent* SelectableComponent = SelectedActor->FindComponentByClass(); + + if (SelectableComponent) + { + SelectableComponent->SelectActor(); + + // Play selection sound. + if (SelectionSoundCooldownRemaining <= 0.0f && IsValid(SelectableComponent->GetSelectedSound())) + { + UGameplayStatics::PlaySound2D(this, SelectableComponent->GetSelectedSound()); + SelectionSoundCooldownRemaining = SelectableComponent->GetSelectedSound()->GetDuration(); + } + } + } + + // Notify listeners. + NotifyOnSelectionChanged(SelectedActors); +} + +void ARTSPlayerController::SaveControlGroup(int32 Index) +{ + if (Index < 0 || Index > 9) + { + return; + } + + // Save control group. + FRTSControlGroup ControlGroup; + ControlGroup.Actors = SelectedActors; + ControlGroups[Index] = ControlGroup; + + UE_LOG(LogRTS, Log, TEXT("Saved selection to control group %d."), Index); +} + +void ARTSPlayerController::SaveControlGroup0() { SaveControlGroup(0); } +void ARTSPlayerController::SaveControlGroup1() { SaveControlGroup(1); } +void ARTSPlayerController::SaveControlGroup2() { SaveControlGroup(2); } +void ARTSPlayerController::SaveControlGroup3() { SaveControlGroup(3); } +void ARTSPlayerController::SaveControlGroup4() { SaveControlGroup(4); } +void ARTSPlayerController::SaveControlGroup5() { SaveControlGroup(5); } +void ARTSPlayerController::SaveControlGroup6() { SaveControlGroup(6); } +void ARTSPlayerController::SaveControlGroup7() { SaveControlGroup(7); } +void ARTSPlayerController::SaveControlGroup8() { SaveControlGroup(8); } +void ARTSPlayerController::SaveControlGroup9() { SaveControlGroup(9); } + +void ARTSPlayerController::LoadControlGroup(int32 Index) +{ + if (Index < 0 || Index > 9) + { + return; + } + + SelectActors(ControlGroups[Index].Actors); + + UE_LOG(LogRTS, Log, TEXT("Loaded selection from control group %d."), Index); +} + +void ARTSPlayerController::LoadControlGroup0() { LoadControlGroup(0); } +void ARTSPlayerController::LoadControlGroup1() { LoadControlGroup(1); } +void ARTSPlayerController::LoadControlGroup2() { LoadControlGroup(2); } +void ARTSPlayerController::LoadControlGroup3() { LoadControlGroup(3); } +void ARTSPlayerController::LoadControlGroup4() { LoadControlGroup(4); } +void ARTSPlayerController::LoadControlGroup5() { LoadControlGroup(5); } +void ARTSPlayerController::LoadControlGroup6() { LoadControlGroup(6); } +void ARTSPlayerController::LoadControlGroup7() { LoadControlGroup(7); } +void ARTSPlayerController::LoadControlGroup8() { LoadControlGroup(8); } +void ARTSPlayerController::LoadControlGroup9() { LoadControlGroup(9); } + +bool ARTSPlayerController::IsConstructionProgressBarHotkeyPressed() const +{ + return bConstructionProgressBarHotkeyPressed; +} + +bool ARTSPlayerController::IsHealthBarHotkeyPressed() const +{ + return bHealthBarHotkeyPressed; +} + +bool ARTSPlayerController::IsProductionProgressBarHotkeyPressed() const +{ + return bProductionProgressBarHotkeyPressed; +} + +bool ARTSPlayerController::CheckCanBeginBuildingPlacement(TSubclassOf BuildingClass) +{ + // Check resources. + URTSConstructionSiteComponent* ConstructionSiteComponent = URTSGameplayLibrary::FindDefaultComponentByClass(BuildingClass); + + if (ConstructionSiteComponent && !PlayerResourcesComponent->CanPayAllResources(ConstructionSiteComponent->GetConstructionCosts())) + { + NotifyOnErrorOccurred(TEXT("Not enough resources.")); + return false; + } + + // Check requirements. + if (SelectedActors.Num() > 0) + { + TSubclassOf MissingRequirement; + + if (URTSGameplayLibrary::GetMissingRequirementFor(this, SelectedActors[0], BuildingClass, MissingRequirement)) + { + URTSNameComponent* NameComponent = URTSGameplayLibrary::FindDefaultComponentByClass(MissingRequirement); + + if (NameComponent) + { + FString ErrorMessage = TEXT("Missing requirement: "); + ErrorMessage.Append(NameComponent->GetName().ToString()); + NotifyOnErrorOccurred(ErrorMessage); + } + else + { + NotifyOnErrorOccurred("Missing requirement."); + } + + return false; + } + } + + return true; +} + +void ARTSPlayerController::BeginBuildingPlacement(TSubclassOf BuildingClass) +{ + // Spawn dummy building. + FActorSpawnParameters SpawnParams; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + + UStaticMeshComponent* StaticMeshComponent = URTSGameplayLibrary::FindDefaultComponentByClass(BuildingClass); + + if (IsValid(StaticMeshComponent)) + { + BuildingCursor = GetWorld()->SpawnActor(BuildingCursorClass, SpawnParams); + BuildingCursor->SetStaticMesh(StaticMeshComponent->GetStaticMesh(), StaticMeshComponent->GetRelativeTransform()); + BuildingCursor->SetLocationValid(false); + } + else + { + USkeletalMeshComponent* SkeletalMeshComponent = URTSGameplayLibrary::FindDefaultComponentByClass(BuildingClass); + + if (IsValid(SkeletalMeshComponent)) + { + BuildingCursor = GetWorld()->SpawnActor(BuildingCursorClass, SpawnParams); + BuildingCursor->SetSkeletalMesh(SkeletalMeshComponent->SkeletalMesh, SkeletalMeshComponent->GetRelativeTransform()); + BuildingCursor->SetLocationValid(false); + } + } + + BuildingBeingPlacedClass = BuildingClass; + + UE_LOG(LogRTS, Log, TEXT("Beginning placement of building %s."), *BuildingClass->GetName()); + + // Notify listeners. + NotifyOnBuildingPlacementStarted(BuildingClass); +} + +bool ARTSPlayerController::CanPlaceBuilding_Implementation(TSubclassOf BuildingClass, const FVector& Location) const +{ + UWorld* World = GetWorld(); + return URTSCollisionLibrary::IsSuitableLocationForActor(World, BuildingClass, Location); +} + +void ARTSPlayerController::Surrender() +{ + if (IsNetMode(NM_Client)) + { + UE_LOG(LogRTS, Log, TEXT("%s surrenders the game."), *GetName()); + } + + ServerSurrender(); +} + +void ARTSPlayerController::GameHasEnded(class AActor* EndGameFocus /*= NULL*/, bool bIsWinner /*= false*/) +{ + ClientGameHasEnded(bIsWinner); +} + +void ARTSPlayerController::ClientGameHasEnded_Implementation(bool bIsWinner) +{ + NotifyOnGameHasEnded(bIsWinner); +} + +void ARTSPlayerController::StartSelectActors() +{ + if (BuildingCursor) + { + // We're selecting a building location instead. + return; + } + + // Get mouse input. + float MouseX; + float MouseY; + + if (GetMousePosition(MouseX, MouseY)) + { + SelectionFrameMouseStartPosition = FVector2D(MouseX, MouseY); + bCreatingSelectionFrame = true; + } +} + +void ARTSPlayerController::FinishSelectActors() +{ + // Get objects at pointer position. + TArray HitResults; + + if (!GetObjectsInSelectionFrame(HitResults)) + { + bCreatingSelectionFrame = false; + return; + } + + // Check results. + TArray ActorsToSelect; + + if (bAddSelectionHotkeyPressed || bToggleSelectionHotkeyPressed) + { + ActorsToSelect = SelectedActors; + } + + for (auto& HitResult : HitResults) + { + if (!IsSelectableActor(HitResult.Actor.Get())) + { + continue; + } + + // Check how to apply selection. + if (bToggleSelectionHotkeyPressed) + { + if (SelectedActors.Contains(HitResult.Actor)) + { + // Deselect actor. + ActorsToSelect.Remove(HitResult.Actor.Get()); + + UE_LOG(LogRTS, Log, TEXT("Deselected actor %s."), *HitResult.Actor->GetName()); + } + else if (!ActorsToSelect.Contains(HitResult.Actor)) + { + // Select actor. + ActorsToSelect.Add(HitResult.Actor.Get()); + + UE_LOG(LogRTS, Log, TEXT("Selected actor %s."), *HitResult.Actor->GetName()); + } + } + else + { + if (ActorsToSelect.Contains(HitResult.Actor)) + { + continue; + } + + // Select actor. + ActorsToSelect.Add(HitResult.Actor.Get()); + + UE_LOG(LogRTS, Log, TEXT("Selected actor %s."), *HitResult.Actor->GetName()); + } + } + + SelectActors(ActorsToSelect); + + bCreatingSelectionFrame = false; +} + +void ARTSPlayerController::StartShowingConstructionProgressBars() +{ + bConstructionProgressBarHotkeyPressed = true; +} + +void ARTSPlayerController::StopShowingConstructionProgressBars() +{ + bConstructionProgressBarHotkeyPressed = false; +} + +void ARTSPlayerController::StartShowingHealthBars() +{ + bHealthBarHotkeyPressed = true; +} + +void ARTSPlayerController::StopShowingHealthBars() +{ + bHealthBarHotkeyPressed = false; +} + +void ARTSPlayerController::StartShowingProductionProgressBars() +{ + bProductionProgressBarHotkeyPressed = true; +} + +void ARTSPlayerController::StopShowingProductionProgressBars() +{ + bProductionProgressBarHotkeyPressed = false; +} + +void ARTSPlayerController::StartAddSelection() +{ + bAddSelectionHotkeyPressed = true; +} + +void ARTSPlayerController::StopAddSelection() +{ + bAddSelectionHotkeyPressed = false; +} + +void ARTSPlayerController::StartToggleSelection() +{ + bToggleSelectionHotkeyPressed = true; +} + +void ARTSPlayerController::StopToggleSelection() +{ + bToggleSelectionHotkeyPressed = false; +} + +void ARTSPlayerController::ConfirmBuildingPlacement() +{ + if (!BuildingCursor) + { + return; + } + + if (!CanPlaceBuilding(BuildingBeingPlacedClass, HoveredWorldPosition)) + { + UE_LOG(LogRTS, Log, TEXT("Can't place building %s at %s."), *BuildingBeingPlacedClass->GetName(), *HoveredWorldPosition.ToString()); + + // Notify listeners. + NotifyOnBuildingPlacementError(BuildingBeingPlacedClass, HoveredWorldPosition); + return; + } + + UE_LOG(LogRTS, Log, TEXT("Placed building %s at %s."), *BuildingBeingPlacedClass->GetName(), *HoveredWorldPosition.ToString()); + + // Remove dummy building. + BuildingCursor->Destroy(); + BuildingCursor = nullptr; + + // Notify listeners. + NotifyOnBuildingPlacementConfirmed(BuildingBeingPlacedClass, HoveredWorldPosition); + + // Start construction. + IssueBeginConstructionOrder(BuildingBeingPlacedClass, HoveredWorldPosition); +} + +void ARTSPlayerController::CancelBuildingPlacement() +{ + if (!BuildingCursor) + { + return; + } + + // Remove dummy building. + BuildingCursor->Destroy(); + BuildingCursor = nullptr; + + UE_LOG(LogRTS, Log, TEXT("Cancelled placement of building %s."), *BuildingBeingPlacedClass->GetName()); + + // Notify listeners. + NotifyOnBuildingPlacementCancelled(BuildingBeingPlacedClass); +} + +void ARTSPlayerController::CancelConstruction() +{ + for (auto SelectedActor : SelectedActors) + { + // Verify construction site and owner. + auto ConstructionSiteComponent = SelectedActor->FindComponentByClass(); + + if (!ConstructionSiteComponent) + { + continue; + } + + if (SelectedActor->GetOwner() != this) + { + continue; + } + + // Send message to server. + ServerCancelConstruction(SelectedActor); + + // Only cancel one construction at a time. + return; + } +} + +void ARTSPlayerController::ServerCancelConstruction_Implementation(AActor* ConstructionSite) +{ + auto ConstructionSiteComponent = ConstructionSite->FindComponentByClass(); + + if (!ConstructionSiteComponent) + { + return; + } + + // Cancel construction. + ConstructionSiteComponent->CancelConstruction(); +} + +bool ARTSPlayerController::ServerCancelConstruction_Validate(AActor* ConstructionSite) +{ + // Verify owner to prevent cheating. + return ConstructionSite->GetOwner() == this; +} + +void ARTSPlayerController::CancelProduction() +{ + for (auto SelectedActor : SelectedActors) + { + // Verify production actor. + auto ProductionComponent = SelectedActor->FindComponentByClass(); + + if (!ProductionComponent) + { + continue; + } + + if (SelectedActor->GetOwner() != this) + { + continue; + } + + if (!URTSGameplayLibrary::IsReadyToUse(SelectedActor)) + { + continue; + } + + // Send message to server. + ServerCancelProduction(SelectedActor); + + // Only cancel one production at a time. + return; + } +} + +void ARTSPlayerController::ServerCancelProduction_Implementation(AActor* ProductionActor) +{ + auto ProductionComponent = ProductionActor->FindComponentByClass(); + + if (!ProductionComponent) + { + return; + } + + // Cancel production. + ProductionComponent->CancelProduction(); +} + +bool ARTSPlayerController::ServerCancelProduction_Validate(AActor* ProductionActor) +{ + // Verify owner to prevent cheating. + return ProductionActor->GetOwner() == this; +} + +void ARTSPlayerController::ServerStartProduction_Implementation(AActor* ProductionActor, TSubclassOf ProductClass) +{ + auto ProductionComponent = ProductionActor->FindComponentByClass(); + + if (!IsValid(ProductionComponent)) + { + return; + } + + ProductionComponent->StartProduction(ProductClass); +} + +bool ARTSPlayerController::ServerStartProduction_Validate(AActor* ProductionActor, TSubclassOf ProductClass) +{ + // Verify owner to prevent cheating. + return ProductionActor->GetOwner() == this; +} + +void ARTSPlayerController::ServerSurrender_Implementation() +{ + UE_LOG(LogRTS, Log, TEXT("%s surrenders the game."), *GetName()); + + // Notify game mode. + ARTSGameMode* GameMode = Cast(UGameplayStatics::GetGameMode(this)); + + if (GameMode != nullptr) + { + GameMode->NotifyOnPlayerDefeated(this); + } +} + +bool ARTSPlayerController::ServerSurrender_Validate() +{ + return true; +} + +void ARTSPlayerController::MoveCameraLeftRight(float Value) +{ + CameraLeftRightAxisValue = Value; +} + +void ARTSPlayerController::MoveCameraUpDown(float Value) +{ + CameraUpDownAxisValue = Value; +} + +void ARTSPlayerController::ZoomCamera(float Value) +{ + CameraZoomAxisValue = Value; +} + +void ARTSPlayerController::NotifyOnActorOwnerChanged(AActor* Actor) +{ + ReceiveOnActorOwnerChanged(Actor); +} + +void ARTSPlayerController::NotifyOnBuildingPlacementStarted(TSubclassOf BuildingClass) +{ + ReceiveOnBuildingPlacementStarted(BuildingClass); +} + +void ARTSPlayerController::NotifyOnBuildingPlacementConfirmed(TSubclassOf BuildingClass, const FVector& Location) +{ + ReceiveOnBuildingPlacementConfirmed(BuildingClass, Location); +} + +void ARTSPlayerController::NotifyOnBuildingPlacementError(TSubclassOf BuildingClass, const FVector& Location) +{ + ReceiveOnBuildingPlacementError(BuildingClass, Location); +} + +void ARTSPlayerController::NotifyOnBuildingPlacementCancelled(TSubclassOf BuildingClass) +{ + ReceiveOnBuildingPlacementCancelled(BuildingClass); +} + +void ARTSPlayerController::NotifyOnErrorOccurred(const FString& ErrorMessage) +{ + ReceiveOnErrorOccurred(ErrorMessage); +} + +void ARTSPlayerController::NotifyOnGameHasEnded(bool bIsWinner) +{ + ReceiveOnGameHasEnded(bIsWinner); +} + +void ARTSPlayerController::NotifyOnIssuedAttackOrder(APawn* OrderedPawn, AActor* Target) +{ + ReceiveOnIssuedAttackOrder(OrderedPawn, Target); +} + +void ARTSPlayerController::NotifyOnIssuedBeginConstructionOrder(APawn* OrderedPawn, TSubclassOf BuildingClass, const FVector& TargetLocation) +{ + ReceiveOnIssuedBeginConstructionOrder(OrderedPawn, BuildingClass, TargetLocation); +} + +void ARTSPlayerController::NotifyOnIssuedContinueConstructionOrder(APawn* OrderedPawn, AActor* ConstructionSite) +{ + ReceiveOnIssuedContinueConstructionOrder(OrderedPawn, ConstructionSite); +} + +void ARTSPlayerController::NotifyOnIssuedGatherOrder(APawn* OrderedPawn, AActor* ResourceSource) +{ + ReceiveOnIssuedGatherOrder(OrderedPawn, ResourceSource); +} + +void ARTSPlayerController::NotifyOnIssuedMoveOrder(APawn* OrderedPawn, const FVector& TargetLocation) +{ + ReceiveOnIssuedMoveOrder(OrderedPawn, TargetLocation); +} + +void ARTSPlayerController::NotifyOnIssuedProductionOrder(AActor* OrderedActor, TSubclassOf ProductClass) +{ + ReceiveOnIssuedProductionOrder(OrderedActor, ProductClass); +} + +void ARTSPlayerController::NotifyOnIssuedStopOrder(APawn* OrderedPawn) +{ + ReceiveOnIssuedStopOrder(OrderedPawn); +} + +void ARTSPlayerController::NotifyOnSelectionChanged(const TArray& Selection) +{ + ReceiveOnSelectionChanged(Selection); +} + +void ARTSPlayerController::NotifyOnTeamChanged(ARTSTeamInfo* NewTeam) +{ + if (NewTeam) + { + // Notify listeners that new vision info is available now. + ARTSVisionInfo* VisionInfo = ARTSVisionInfo::GetVisionInfoForTeam(GetWorld(), NewTeam->GetTeamIndex()); + NotifyOnVisionInfoAvailable(VisionInfo); + } +} + +void ARTSPlayerController::NotifyOnVisionInfoAvailable(ARTSVisionInfo* VisionInfo) +{ + // On server side, we're only interested in our own vision info. + // Other player controllers that exist on the server for replication to the clients + // should not affect rendering vision for the local player. + if (this != GetWorld()->GetFirstPlayerController()) + { + return; + } + + // Setup fog of war. + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + ARTSFogOfWarActor* FogOfWarActor = *ActorIt; + FogOfWarActor->SetupVisionInfo(VisionInfo); + break; + } + + // Allow others to setup vision. + ReceiveOnVisionInfoAvailable(VisionInfo); +} + +void ARTSPlayerController::NotifyOnMinimapClicked(const FPointerEvent& InMouseEvent, const FVector2D& MinimapPosition, const FVector& WorldPosition) +{ + APawn* PlayerPawn = GetPawn(); + + if (!PlayerPawn) + { + return; + } + + if (InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + // Move camera. + FVector NewCameraLocation = FVector(WorldPosition.X, WorldPosition.Y, PlayerPawn->GetActorLocation().Z); + PlayerPawn->SetActorLocation(NewCameraLocation); + } + else if (InMouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)) + { + // Get objects at pointer position. + TArray HitResults; + + if (!GetObjectsAtWorldPosition(WorldPosition, HitResults)) + { + return; + } + + IssueOrderTargetingObjects(HitResults); + } + + // Notify listeners. + ReceiveOnMinimapClicked(InMouseEvent, MinimapPosition, WorldPosition); +} + +void ARTSPlayerController::PlayerTick(float DeltaTime) +{ + Super::PlayerTick(DeltaTime); + + // Update sound cooldowns. + if (SelectionSoundCooldownRemaining > 0.0f) + { + SelectionSoundCooldownRemaining -= DeltaTime; + } + + APawn* PlayerPawn = GetPawn(); + + if (!PlayerPawn) + { + return; + } + + // Get mouse input. + float MouseX; + float MouseY; + + const FVector2D ViewportSize = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY()); + + const float ScrollBorderRight = ViewportSize.X - CameraScrollThreshold; + const float ScrollBorderTop = ViewportSize.Y - CameraScrollThreshold; + + if (GetMousePosition(MouseX, MouseY)) + { + if (MouseX <= CameraScrollThreshold) + { + CameraLeftRightAxisValue -= 1 - (MouseX / CameraScrollThreshold); + } + else if (MouseX >= ScrollBorderRight) + { + CameraLeftRightAxisValue += (MouseX - ScrollBorderRight) / CameraScrollThreshold; + } + + if (MouseY <= CameraScrollThreshold) + { + CameraUpDownAxisValue += 1 - (MouseY / CameraScrollThreshold); + } + else if (MouseY >= ScrollBorderTop) + { + CameraUpDownAxisValue -= (MouseY - ScrollBorderTop) / CameraScrollThreshold; + } + } + + // Apply input. + CameraLeftRightAxisValue = FMath::Clamp(CameraLeftRightAxisValue, -1.0f, +1.0f); + CameraUpDownAxisValue = FMath::Clamp(CameraUpDownAxisValue, -1.0f, +1.0f); + + FVector Location = PlayerPawn->GetActorLocation(); + Location += FVector::RightVector * CameraSpeed * CameraLeftRightAxisValue * DeltaTime; + Location += FVector::ForwardVector * CameraSpeed * CameraUpDownAxisValue * DeltaTime; + + // Enforce camera bounds. + if (!CameraBoundsVolume || CameraBoundsVolume->EncompassesPoint(Location)) + { + PlayerPawn->SetActorLocation(Location); + } + + // Apply zoom input. + UCameraComponent* PlayerPawnCamera = PlayerPawn->FindComponentByClass(); + + if (IsValid(PlayerPawnCamera)) + { + FVector CameraLocation = PlayerPawnCamera->GetRelativeLocation(); + CameraLocation.Z += CameraZoomSpeed * CameraZoomAxisValue * DeltaTime; + CameraLocation.Z = FMath::Clamp(CameraLocation.Z, MinCameraDistance, MaxCameraDistance); + PlayerPawnCamera->SetRelativeLocation(CameraLocation); + } + + // Get hovered actors. + HoveredActor = nullptr; + + TArray HitResults; + + if (GetObjectsAtPointerPosition(HitResults)) + { + for (auto& HitResult : HitResults) + { + // Store hovered world position. + HoveredWorldPosition = HitResult.Location; + + // Update position of building being placed. + if (BuildingCursor) + { + BuildingCursor->SetActorLocation(HoveredWorldPosition); + + bool bLocationValid = CanPlaceBuilding(BuildingBeingPlacedClass, HoveredWorldPosition); + BuildingCursor->SetLocationValid(bLocationValid); + } + + // Check if hit any actor. + if (HitResult.Actor == nullptr || Cast(HitResult.Actor.Get()) != nullptr) + { + continue; + } + + // Check if hit selectable actor. + auto SelectableComponent = HitResult.Actor->FindComponentByClass(); + + if (!SelectableComponent) + { + continue; + } + + // Set hovered actor. + HoveredActor = HitResult.Actor.Get(); + } + } + + // Verify selection. + SelectedActors.RemoveAll([=](AActor* SelectedActor) { return SelectedActor->IsHidden(); }); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerStart.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerStart.cpp new file mode 100644 index 00000000..4d96b24e --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerStart.cpp @@ -0,0 +1,16 @@ +#include "RTSPlayerStart.h" + +int32 ARTSPlayerStart::GetTeamIndex() const +{ + return TeamIndex; +} + +AController* ARTSPlayerStart::GetPlayer() const +{ + return Player; +} + +void ARTSPlayerStart::SetPlayer(AController* InPlayer) +{ + Player = InPlayer; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerState.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerState.cpp new file mode 100644 index 00000000..256de4ce --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPlayerState.cpp @@ -0,0 +1,91 @@ +#include "RTSPlayerState.h" + +#include "Net/UnrealNetwork.h" + +#include "RTSLog.h" +#include "RTSPlayerController.h" +#include "RTSTeamInfo.h" + + +const uint8 ARTSPlayerState::PLAYER_INDEX_NONE = 255; + + +ARTSPlayerState::ARTSPlayerState(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PlayerIndex = PLAYER_INDEX_NONE; +} + +void ARTSPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ARTSPlayerState, PlayerIndex); + DOREPLIFETIME(ARTSPlayerState, Team); +} + +uint8 ARTSPlayerState::GetPlayerIndex() const +{ + return PlayerIndex; +} + +ARTSTeamInfo* ARTSPlayerState::GetTeam() const +{ + return Team; +} + +void ARTSPlayerState::SetPlayerIndex(uint8 InPlayerIndex) +{ + PlayerIndex = InPlayerIndex; +} + +void ARTSPlayerState::SetTeam(ARTSTeamInfo* InTeam) +{ + Team = InTeam; +} + +bool ARTSPlayerState::IsSameTeamAs(ARTSPlayerState* Other) const +{ + if (!Other) + { + return false; + } + + ARTSTeamInfo* FirstTeam = Team; + ARTSTeamInfo* SecondTeam = Other->Team; + + if (!FirstTeam || !SecondTeam) + { + return false; + } + + return FirstTeam->GetTeamIndex() == SecondTeam->GetTeamIndex(); +} + +void ARTSPlayerState::NotifyOnTeamChanged(ARTSTeamInfo* NewTeam) +{ + if (NewTeam) + { + UE_LOG(LogRTS, Log, TEXT("Player %s added to team %d."), *GetName(), NewTeam->GetTeamIndex()); + } + else + { + UE_LOG(LogRTS, Log, TEXT("Player %s added to team None."), *GetName()); + } + + // Notify listeners. + ReceiveOnTeamChanged(NewTeam); + + // Notify player. + ARTSPlayerController* PlayerController = Cast(GetOwner()); + + if (PlayerController) + { + PlayerController->NotifyOnTeamChanged(NewTeam); + } +} + +void ARTSPlayerState::OnTeamChanged() +{ + NotifyOnTeamChanged(Team); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPortraitComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPortraitComponent.cpp new file mode 100644 index 00000000..dd834494 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSPortraitComponent.cpp @@ -0,0 +1,9 @@ +#include "RTSPortraitComponent.h" + +#include "Engine/Texture2D.h" + + +UTexture2D* URTSPortraitComponent::GetPortrait() const +{ + return Portrait; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSRequirementsComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSRequirementsComponent.cpp new file mode 100644 index 00000000..b77bbbc9 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSRequirementsComponent.cpp @@ -0,0 +1,6 @@ +#include "RTSRequirementsComponent.h" + +TArray> URTSRequirementsComponent::GetRequiredActors() const +{ + return RequiredActors; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSSelectableComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSSelectableComponent.cpp new file mode 100644 index 00000000..0073f9e6 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSSelectableComponent.cpp @@ -0,0 +1,96 @@ +#include "RTSSelectableComponent.h" + +#include "WorldCollision.h" +#include "Components/DecalComponent.h" +#include "GameFramework/Actor.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Materials/MaterialInstanceDynamic.h" + +#include "RTSLog.h" + + +void URTSSelectableComponent::BeginPlay() +{ + Super::BeginPlay(); + + AActor* Owner = GetOwner(); + + if (!IsValid(Owner)) + { + return; + } + + // Calculate decal size. + float DecalHeight = URTSCollisionLibrary::GetActorCollisionHeight(Owner); + float DecalRadius = URTSCollisionLibrary::GetActorCollisionSize(Owner); + + // Create selection circle decal. + DecalComponent = NewObject(Owner, TEXT("SelectionCircleDecal")); + + if (!DecalComponent) + { + return; + } + + // Set decal size. + DecalComponent->RegisterComponent(); + DecalComponent->AttachToComponent(Owner->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + DecalComponent->DecalSize = FVector(DecalHeight, DecalRadius, DecalRadius); + + // Rotate decal to face ground. + DecalComponent->SetRelativeRotation(FRotator::MakeFromEuler(FVector(0.0f, -90.0f, 0.0f))); + + // Setup decal material. + SelectionCircleMaterialInstance = UMaterialInstanceDynamic::Create(SelectionCircleMaterial, this); + DecalComponent->SetDecalMaterial(SelectionCircleMaterialInstance); + + DecalComponent->SetHiddenInGame(true); +} + +void URTSSelectableComponent::SelectActor() +{ + if (bSelected) + { + return; + } + + bSelected = true; + + // Update selection circle. + if (IsValid(DecalComponent)) + { + DecalComponent->SetHiddenInGame(false); + } + + // Notify listeners. + OnSelected.Broadcast(GetOwner()); +} + +void URTSSelectableComponent::DeselectActor() +{ + if (!bSelected) + { + return; + } + + bSelected = false; + + // Update selection circles. + if (IsValid(DecalComponent)) + { + DecalComponent->SetHiddenInGame(true); + } + + // Notify listeners. + OnDeselected.Broadcast(GetOwner()); +} + +bool URTSSelectableComponent::IsSelected() const +{ + return bSelected; +} + +USoundCue* URTSSelectableComponent::GetSelectedSound() const +{ + return SelectedSound; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSTeamInfo.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSTeamInfo.cpp new file mode 100644 index 00000000..7c22068c --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RTSTeamInfo.cpp @@ -0,0 +1,88 @@ +#include "RTSTeamInfo.h" + +#include "Engine/World.h" +#include "Net/UnrealNetwork.h" + +#include "RTSGameState.h" +#include "RTSPlayerState.h" + + +ARTSTeamInfo::ARTSTeamInfo(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + SetReplicates(true); + bAlwaysRelevant = true; + NetUpdateFrequency = 1.0f; + + // Force ReceivedTeamIndex() on clients. + TeamIndex = 255; +} + +void ARTSTeamInfo::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(ARTSTeamInfo, TeamIndex, COND_InitialOnly); +} + +void ARTSTeamInfo::AddToTeam(AController* Player) +{ + if (Player == nullptr) + { + return; + } + + ARTSPlayerState* PlayerState = Cast(Player->PlayerState); + if (PlayerState == nullptr) + { + return; + } + + if (PlayerState->GetTeam() != nullptr) + { + RemoveFromTeam(Player); + } + + PlayerState->SetTeam(this); + PlayerState->NotifyOnTeamChanged(this); + + TeamMembers.Add(Player); +} + +void ARTSTeamInfo::RemoveFromTeam(AController* Player) +{ + if (Player == nullptr) + { + return; + } + + if (!TeamMembers.Contains(Player)) + { + return; + } + + TeamMembers.Remove(Player); + + ARTSPlayerState* PlayerState = Cast(Player->PlayerState); + + if (PlayerState != nullptr) + { + PlayerState->SetTeam(nullptr); + PlayerState->NotifyOnTeamChanged(nullptr); + } +} + +uint8 ARTSTeamInfo::GetTeamIndex() const +{ + return TeamIndex; +} + +TArray ARTSTeamInfo::GetTeamMembers() const +{ + return TeamMembers; +} + +void ARTSTeamInfo::SetTeamIndex(uint8 InTeamIndex) +{ + TeamIndex = InTeamIndex; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RealTimeStrategy.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RealTimeStrategy.cpp new file mode 100644 index 00000000..b09241b6 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RealTimeStrategy.cpp @@ -0,0 +1,17 @@ +#include "RealTimeStrategy.h" + +#include "Modules/ModuleManager.h" + + +void FRealTimeStrategy::StartupModule() +{ + // This code will execute after your module is loaded into memory (but after global variables are initialized, of course.) +} + +void FRealTimeStrategy::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +IMPLEMENT_MODULE(FRealTimeStrategy, RealTimeStrategy) diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RealTimeStrategy.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RealTimeStrategy.h new file mode 100644 index 00000000..c95a8f34 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/RealTimeStrategy.h @@ -0,0 +1,10 @@ +#pragma once + +#include "IRealTimeStrategy.h" + +class FRealTimeStrategy : public IRealTimeStrategy +{ + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSActorWidgetComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSActorWidgetComponent.cpp new file mode 100644 index 00000000..027d44b1 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSActorWidgetComponent.cpp @@ -0,0 +1,9 @@ +#include "UI/RTSActorWidgetComponent.h" + + +URTSActorWidgetComponent::URTSActorWidgetComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + Space = EWidgetSpace::Screen; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSFloatingCombatTextComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSFloatingCombatTextComponent.cpp new file mode 100644 index 00000000..25a9c7ff --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSFloatingCombatTextComponent.cpp @@ -0,0 +1,39 @@ +#include "UI/RTSFloatingCombatTextComponent.h" + + +URTSFloatingCombatTextComponent::URTSFloatingCombatTextComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; +} + +void URTSFloatingCombatTextComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + for (int32 Index = Texts.Num() - 1; Index >= 0; --Index) + { + FRTSFloatingCombatTextData& TextData = Texts[Index]; + TextData.RemainingLifetime -= DeltaTime; + + if (TextData.RemainingLifetime <= 0.0f) + { + Texts.RemoveAt(Index); + } + } +} + +void URTSFloatingCombatTextComponent::AddText(const FString& Text, const FLinearColor& Color, float Scale, float Lifetime) +{ + FRTSFloatingCombatTextData TextData; + TextData.Text = Text; + TextData.Color = Color; + TextData.Scale = Scale; + TextData.Lifetime = Lifetime; + TextData.RemainingLifetime = Lifetime; + + Texts.Add(TextData); +} + +TArray URTSFloatingCombatTextComponent::GetTexts() const +{ + return Texts; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSHUD.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSHUD.cpp new file mode 100644 index 00000000..b5debae2 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSHUD.cpp @@ -0,0 +1,476 @@ +#include "UI/RTSHUD.h" + +#include "EngineUtils.h" + +#include "RTSPlayerController.h" +#include "Combat/RTSHealthComponent.h" +#include "Combat/RTSHealthBarWidgetComponent.h" +#include "Construction/RTSConstructionSiteComponent.h" +#include "Construction/RTSConstructionProgressBarWidgetComponent.h" +#include "Libraries/RTSCollisionLibrary.h" +#include "Production/RTSProductionComponent.h" +#include "Production/RTSProductionProgressBarWidgetComponent.h" +#include "UI/RTSFloatingCombatTextComponent.h" +#include "UI/RTSFloatingCombatTextData.h" +#include "UI/RTSHoveredActorWidgetComponent.h" + + +void ARTSHUD::DrawHUD() +{ + Super::DrawHUD(); + + DrawFloatingCombatTexts(); + DrawSelectionFrame(); + DrawHealthBars(); + DrawConstructionProgressBars(); + DrawProductionProgressBars(); + DrawHoveredActorWidget(); +} + +void ARTSHUD::NotifyDrawFloatingCombatText(AActor* Actor, const FString& Text, const FLinearColor& Color, float Scale, float Lifetime, float RemainingLifetime, float LifetimePercentage, float SuggestedTextLeft, float SuggestedTextTop) +{ + ReceiveDrawFloatingCombatText( + Actor, + Text, + Color, + Scale, + Lifetime, + RemainingLifetime, + LifetimePercentage, + SuggestedTextLeft, + SuggestedTextTop); +} + +void ARTSHUD::NotifyDrawSelectionFrame(float ScreenX, float ScreenY, float Width, float Height) +{ + ReceiveDrawSelectionFrame(ScreenX, ScreenY, Width, Height); +} + +void ARTSHUD::NotifyHideSelectionFrame() +{ + ReceiveHideSelectionFrame(); +} + +FVector2D ARTSHUD::GetActorCenterOnScreen(AActor* Actor) const +{ + FVector ProjectedLocation = Project(Actor->GetActorLocation()); + return FVector2D(ProjectedLocation.X, ProjectedLocation.Y); +} + +FVector2D ARTSHUD::GetActorSizeOnScreen(AActor* Actor) const +{ + // Get actor position projected on HUD. + float ActorHeight = URTSCollisionLibrary::GetActorCollisionHeight(Actor); + float ActorWidth = URTSCollisionLibrary::GetActorCollisionSize(Actor); + + FVector ActorTopPosition = Project(Actor->GetActorLocation() + (Actor->GetActorForwardVector() * ActorHeight)); + FVector ActorBottomPosition = Project(Actor->GetActorLocation() - (Actor->GetActorForwardVector() * ActorHeight)); + FVector ActorLeftPosition = Project(Actor->GetActorLocation() - (Actor->GetActorRightVector() * ActorWidth)); + FVector ActorRightPosition = Project(Actor->GetActorLocation() + (Actor->GetActorRightVector() * ActorWidth)); + + float Width = FVector(ActorRightPosition - ActorLeftPosition).Size(); + float Height = FVector(ActorTopPosition - ActorBottomPosition).Size(); + + return FVector2D(Width, Height); +} + +void ARTSHUD::DrawSelectionFrame() +{ + // Get selection frame. + ARTSPlayerController* PlayerController = Cast(PlayerOwner); + + if (!PlayerController) + { + return; + } + + FIntRect SelectionFrame; + + if (!PlayerController->GetSelectionFrame(SelectionFrame)) + { + if (bWasDrawingSelectionFrame) + { + NotifyHideSelectionFrame(); + } + + bWasDrawingSelectionFrame = false; + return; + } + + // Draw selection frame. + NotifyDrawSelectionFrame( + SelectionFrame.Min.X, + SelectionFrame.Min.Y, + SelectionFrame.Width(), + SelectionFrame.Height()); + + bWasDrawingSelectionFrame = true; +} + +void ARTSHUD::DrawFloatingCombatTexts() +{ + if (!bShowFloatingCombatTexts) + { + return; + } + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + + if (!IsValid(Actor)) + { + return; + } + + // Check floating combat texts. + URTSFloatingCombatTextComponent* FloatingCombatTextComponent = Actor->FindComponentByClass(); + + if (!FloatingCombatTextComponent) + { + continue; + } + + for (FRTSFloatingCombatTextData& TextData : FloatingCombatTextComponent->GetTexts()) + { + // Calculate lifetime. + float ElapsedLifetime = TextData.Lifetime - TextData.RemainingLifetime; + float LifetimePercentage = ElapsedLifetime / TextData.Lifetime; + + // Calculate position. + FVector2D Center = GetActorCenterOnScreen(Actor); + FVector2D Size = GetActorSizeOnScreen(Actor); + + // Calculate color. + FLinearColor TextColor = TextData.Color; + + if (bFadeOutFloatingCombatTexts) + { + TextColor.A = 1 - LifetimePercentage; + } + + // Draw text. + NotifyDrawFloatingCombatText( + Actor, + TextData.Text, + TextColor, + TextData.Scale, + TextData.Lifetime, + TextData.RemainingLifetime, + LifetimePercentage, + Center.X, + Center.Y - (Size.Y / 2) - (FloatingCombatTextSpeed * ElapsedLifetime)); + } + } +} + +void ARTSHUD::DrawHealthBars() +{ + ARTSPlayerController* PlayerController = Cast(PlayerOwner); + + if (!PlayerController) + { + return; + } + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + + // Check override conditions. + if (bAlwaysShowHealthBars || (bShowHotkeyHealthBars && PlayerController->IsHealthBarHotkeyPressed())) + { + // Draw all health bars. + DrawHealthBar(Actor); + } + else if (bShowHoverHealthBars && Actor == PlayerController->GetHoveredActor()) + { + // Draw health bar for hovered actor. + DrawHealthBar(Actor); + } + else if (bShowSelectionHealthBars && PlayerController->GetSelectedActors().Contains(Actor)) + { + // Draw health bars for selected actors. + DrawHealthBar(Actor); + } + else + { + HideHealthBar(Actor); + } + } +} + +void ARTSHUD::DrawHealthBar(AActor* Actor) +{ + if (!IsValid(Actor)) + { + return; + } + + // Check health. + URTSHealthComponent* HealthComponent = Actor->FindComponentByClass(); + + if (!IsValid(HealthComponent)) + { + return; + } + + const float HealthPercentage = HealthComponent->GetCurrentHealth() / HealthComponent->GetMaximumHealth(); + + // Draw health bar. + URTSHealthBarWidgetComponent* HealthBarWidgetComponent = Actor->FindComponentByClass(); + + if (!IsValid(HealthBarWidgetComponent)) + { + return; + } + + FVector2D Size = GetActorSizeOnScreen(Actor); + + HealthBarWidgetComponent->UpdatePositionAndSize(Size); + HealthBarWidgetComponent->SetVisibility(true); +} + +void ARTSHUD::HideHealthBar(AActor* Actor) +{ + if (!IsValid(Actor)) + { + return; + } + + URTSHealthBarWidgetComponent* HealthBarWidgetComponent = Actor->FindComponentByClass(); + + if (!IsValid(HealthBarWidgetComponent)) + { + return; + } + + HealthBarWidgetComponent->SetVisibility(false); +} + +void ARTSHUD::DrawConstructionProgressBars() +{ + ARTSPlayerController* PlayerController = Cast(PlayerOwner); + + if (!PlayerController) + { + return; + } + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + + // Check override conditions. + if (bAlwaysShowConstructionProgressBars || (bShowHotkeyConstructionProgressBars && PlayerController->IsConstructionProgressBarHotkeyPressed())) + { + // Draw all progress bars. + DrawConstructionProgressBar(Actor); + } + else if (bShowHoverConstructionProgressBars && Actor == PlayerController->GetHoveredActor()) + { + // Draw progress bar for hovered actor. + DrawConstructionProgressBar(Actor); + } + else if (bShowSelectionConstructionProgressBars && PlayerController->GetSelectedActors().Contains(Actor)) + { + // Draw progress bars for selected actors. + DrawConstructionProgressBar(Actor); + } + else + { + HideConstructionProgressBar(Actor); + } + } +} + +void ARTSHUD::DrawConstructionProgressBar(AActor* Actor) +{ + if (!IsValid(Actor)) + { + return; + } + + // Check progress. + URTSConstructionSiteComponent* ConstructionSiteComponent = Actor->FindComponentByClass(); + + if (!ConstructionSiteComponent) + { + return; + } + + if (!ConstructionSiteComponent->IsConstructing()) + { + return; + } + + const float ProgressPercentage = ConstructionSiteComponent->GetProgressPercentage(); + + // Draw progress bar. + URTSConstructionProgressBarWidgetComponent* ConstructionProgressBarWidgetComponent = + Actor->FindComponentByClass(); + + if (!IsValid(ConstructionProgressBarWidgetComponent)) + { + return; + } + + FVector2D Size = GetActorSizeOnScreen(Actor); + + ConstructionProgressBarWidgetComponent->UpdatePositionAndSize(Size); + ConstructionProgressBarWidgetComponent->SetVisibility(true); +} + +void ARTSHUD::HideConstructionProgressBar(AActor* Actor) +{ + if (!IsValid(Actor)) + { + return; + } + + URTSConstructionProgressBarWidgetComponent* ConstructionProgressBarWidgetComponent = + Actor->FindComponentByClass(); + + if (!IsValid(ConstructionProgressBarWidgetComponent)) + { + return; + } + + ConstructionProgressBarWidgetComponent->SetVisibility(false); +} + +void ARTSHUD::DrawHoveredActorWidget() +{ + ARTSPlayerController* PlayerController = Cast(PlayerOwner); + + if (!PlayerController) + { + return; + } + + AActor* NewHoveredActor = PlayerController->GetHoveredActor(); + + if (NewHoveredActor == OldHoveredActor) + { + return; + } + + if (IsValid(OldHoveredActor)) + { + URTSHoveredActorWidgetComponent* OldHoveredActorWidgetComponent = + OldHoveredActor->FindComponentByClass(); + + if (IsValid(OldHoveredActorWidgetComponent)) + { + OldHoveredActorWidgetComponent->SetVisibility(false); + } + } + + if (IsValid(NewHoveredActor)) + { + URTSHoveredActorWidgetComponent* NewHoveredActorWidgetComponent = + NewHoveredActor->FindComponentByClass(); + + if (IsValid(NewHoveredActorWidgetComponent)) + { + FVector2D Size = GetActorSizeOnScreen(NewHoveredActor); + + NewHoveredActorWidgetComponent->UpdatePositionAndSize(Size); + NewHoveredActorWidgetComponent->UpdateData(NewHoveredActor); + NewHoveredActorWidgetComponent->SetVisibility(true); + } + } + + OldHoveredActor = NewHoveredActor; +} + +void ARTSHUD::DrawProductionProgressBars() +{ + ARTSPlayerController* PlayerController = Cast(PlayerOwner); + + if (!PlayerController) + { + return; + } + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + + // Check override conditions. + if (bAlwaysShowProductionProgressBars || (bShowHotkeyProductionProgressBars && PlayerController->IsProductionProgressBarHotkeyPressed())) + { + // Draw all progress bars. + DrawProductionProgressBar(Actor); + } + else if (bShowHoverProductionProgressBars && Actor == PlayerController->GetHoveredActor()) + { + // Draw progress bar for hovered actor. + DrawProductionProgressBar(Actor); + } + else if (bShowSelectionProductionProgressBars && PlayerController->GetSelectedActors().Contains(Actor)) + { + // Draw progress bars for selected actors. + DrawProductionProgressBar(Actor); + } + else + { + HideProductionProgressBar(Actor); + } + } +} + +void ARTSHUD::DrawProductionProgressBar(AActor* Actor) +{ + if (!IsValid(Actor)) + { + return; + } + + // Check progress. + URTSProductionComponent* ProductionComponent = Actor->FindComponentByClass(); + + if (!ProductionComponent) + { + return; + } + + if (!ProductionComponent->IsProducing()) + { + return; + } + + const float ProgressPercentage = ProductionComponent->GetProgressPercentage(); + + // Draw progress bar. + URTSProductionProgressBarWidgetComponent* ProductionProgressBarWidgetComponent = + Actor->FindComponentByClass(); + + if (!IsValid(ProductionProgressBarWidgetComponent)) + { + return; + } + + FVector2D Size = GetActorSizeOnScreen(Actor); + + ProductionProgressBarWidgetComponent->UpdatePositionAndSize(Size); + ProductionProgressBarWidgetComponent->SetVisibility(true); +} + +void ARTSHUD::HideProductionProgressBar(AActor* Actor) +{ + if (!IsValid(Actor)) + { + return; + } + + URTSProductionProgressBarWidgetComponent* ProductionProgressBarWidgetComponent = + Actor->FindComponentByClass(); + + if (!IsValid(ProductionProgressBarWidgetComponent)) + { + return; + } + + ProductionProgressBarWidgetComponent->SetVisibility(false); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSMinimapVolume.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSMinimapVolume.cpp new file mode 100644 index 00000000..8aa4a0b4 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSMinimapVolume.cpp @@ -0,0 +1,7 @@ +#include "UI/RTSMinimapVolume.h" + + +UTexture2D* ARTSMinimapVolume::GetMinimapImage() const +{ + return MinimapImage; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSMinimapWidget.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSMinimapWidget.cpp new file mode 100644 index 00000000..9dc9606b --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/UI/RTSMinimapWidget.cpp @@ -0,0 +1,394 @@ +#include "UI/RTSMinimapWidget.h" + +#include "EngineUtils.h" +#include "Components/BrushComponent.h" +#include "Kismet/GameplayStatics.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Rendering/DrawElements.h" + +#include "RTSLog.h" +#include "RTSOwnerComponent.h" +#include "RTSPlayerController.h" +#include "RTSPlayerState.h" +#include "UI/RTSMinimapVolume.h" +#include "Vision/RTSFogOfWarActor.h" +#include "Vision/RTSVisionInfo.h" +#include "Vision/RTSVisionState.h" +#include "Vision/RTSVisionVolume.h" + + +void URTSMinimapWidget::NotifyOnDrawUnit( + FPaintContext& Context, + AActor* Actor, + APlayerState* ActorOwner, + const FVector2D& MinimapPosition, + APlayerState* LocalPlayer) const +{ + ReceiveOnDrawUnit(Context, Actor, ActorOwner, MinimapPosition, LocalPlayer); +} + +void URTSMinimapWidget::NativeConstruct() +{ + UUserWidget::NativeConstruct(); + + // Get minimap bounds. + for (TActorIterator It(GetWorld()); It; ++It) + { + MinimapVolume = *It; + break; + } + + if (MinimapVolume) + { + // Set size. + UBrushComponent* MinimapBrushComponent = MinimapVolume->GetBrushComponent(); + FBoxSphereBounds MinimapBounds = MinimapBrushComponent->CalcBounds(MinimapBrushComponent->GetComponentTransform()); + + MinimapWorldSize = MinimapBounds.BoxExtent * 2; + + // Set background image. + MinimapBackground.SetResourceObject(MinimapVolume->GetMinimapImage()); + } + else + { + UE_LOG(LogRTS, Warning, TEXT("No RTSMinimapVolume found. Minimap won't be showing unit positions.")); + } + + // Get fog of war actor. + if (bDrawVision) + { + for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) + { + FogOfWarActor = *ActorItr; + break; + } + + if (FogOfWarActor) + { + // Setup fog of war material. + FogOfWarMaterialInstance = UMaterialInstanceDynamic::Create(FogOfWarMaterial, nullptr); + } + else + { + UE_LOG(LogRTS, Warning, TEXT("No fog of war actor found, won't draw vision on minimap.")); + bDrawVision = false; + } + } +} + +void URTSMinimapWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + if (!IsVisible()) + { + return; + } + + if (!bDrawVision) + { + return; + } + + if (!IsValid(FogOfWarActor) || !IsValid(FogOfWarActor->GetFogOfWarTexture()) || !IsValid(FogOfWarMaterialInstance)) + { + return; + } + + // Update material. + FogOfWarMaterialInstance->SetTextureParameterValue(FName(TEXT("VisibilityMask")), + FogOfWarActor->GetFogOfWarTexture()); + + FogOfWarBrush.ImageSize = + FVector2D(FogOfWarActor->GetFogOfWarTexture()->GetSizeX(), FogOfWarActor->GetFogOfWarTexture()->GetSizeY()); + FogOfWarBrush.SetResourceObject(FogOfWarMaterialInstance); +} + +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 20) +int32 URTSMinimapWidget::NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + LayerId = UUserWidget::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + FPaintContext Context(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + Context.MaxLayer++; + + DrawBackground(Context); + DrawUnits(Context); + DrawVision(Context); + DrawViewFrustum(Context); + + return FMath::Max(LayerId, Context.MaxLayer); +} +#else +void URTSMinimapWidget::NativePaint(FPaintContext& InContext) const +{ + UUserWidget::NativePaint(InContext); + + InContext.MaxLayer++; + + DrawBackground(InContext); + DrawUnits(InContext); + DrawVision(InContext); + DrawViewFrustum(InContext); +} +#endif + +FReply URTSMinimapWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + // Apply default handling. + auto Reply = UUserWidget::NativeOnMouseButtonDown(InGeometry, InMouseEvent); + + if (Reply.IsEventHandled()) + { + return Reply; + } + + // Handle MouseMove events from now on. + bMouseDown = true; + + // Handle initial click. + return HandleMinimapClick(InGeometry, InMouseEvent); +} + +FReply URTSMinimapWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + // Apply default handling. + auto Reply = UUserWidget::NativeOnMouseButtonUp(InGeometry, InMouseEvent); + + if (Reply.IsEventHandled()) + { + return Reply; + } + + // Stop handling MouseMove events. + bMouseDown = false; + return FReply::Handled(); +} + +FReply URTSMinimapWidget::NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + // Apply default handling. + auto Reply = UUserWidget::NativeOnMouseMove(InGeometry, InMouseEvent); + + if (Reply.IsEventHandled()) + { + return Reply; + } + + if (bMouseDown) + { + // Handle movement. + return HandleMinimapClick(InGeometry, InMouseEvent); + } + + return FReply::Unhandled(); +} + +void URTSMinimapWidget::DrawBackground(FPaintContext& InContext) const +{ + if (!bDrawBackground) + { + return; + } + + DrawBoxWithBrush(InContext, FVector2D(0, 0), MinimapBackground); +} + +void URTSMinimapWidget::DrawUnits(FPaintContext& InContext) const +{ + if (!MinimapVolume) + { + return; + } + + APlayerController* Player = GetOwningPlayer(); + + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + URTSOwnerComponent* OwnerComponent = Actor->FindComponentByClass(); + + FVector ActorLocationWorld = Actor->GetActorLocation(); + FVector2D ActorLocationMinimap = WorldToMinimap(ActorLocationWorld); + + // Draw on minimap. + if (bDrawUnitsWithTeamColors && OwnerComponent) + { + if (OwnerComponent->GetPlayerOwner() == Player->PlayerState) + { + DrawBoxWithBrush(InContext, ActorLocationMinimap, OwnUnitsBrush); + } + else if (OwnerComponent->GetPlayerOwner() != nullptr && !OwnerComponent->IsSameTeamAsController(Player)) + { + DrawBoxWithBrush(InContext, ActorLocationMinimap, EnemyUnitsBrush); + } + else + { + DrawBoxWithBrush(InContext, ActorLocationMinimap, NeutralUnitsBrush); + } + } + + // Allow custom drawing. + NotifyOnDrawUnit( + InContext, + Actor, + OwnerComponent ? OwnerComponent->GetPlayerOwner() : nullptr, + ActorLocationMinimap, + Player->PlayerState); + } +} + +void URTSMinimapWidget::DrawVision(FPaintContext& InContext) const +{ + if (!bDrawVision) + { + return; + } + + if (!FogOfWarActor || !FogOfWarActor->GetFogOfWarTexture() || !FogOfWarMaterialInstance) + { + return; + } + + DrawBoxWithBrush(InContext, FVector2D::ZeroVector, FogOfWarBrush); +} + +void URTSMinimapWidget::DrawViewFrustum(FPaintContext& InContext) const +{ + if (!bDrawViewFrustum) + { + return; + } + + // Get viewport size. + ARTSPlayerController* Player = Cast(GetOwningPlayer()); + + if (!Player) + { + return; + } + + int32 ViewportWidth; + int32 ViewportHeight; + + Player->GetViewportSize(ViewportWidth, ViewportHeight); + + // Cast four rays. + FVector2D ViewportTopLeft(0, 0); + FVector2D ViewportTopRight(ViewportWidth, 0); + FVector2D ViewportBottomLeft(0, ViewportHeight); + FVector2D ViewportBottomRight(ViewportWidth, ViewportHeight); + + FVector WorldTopLeft; + FVector WorldTopRight; + FVector WorldBottomLeft; + FVector WorldBottomRight; + + ViewportToWorld(Player, ViewportTopLeft, WorldTopLeft); + ViewportToWorld(Player, ViewportTopRight, WorldTopRight); + ViewportToWorld(Player, ViewportBottomLeft, WorldBottomLeft); + ViewportToWorld(Player, ViewportBottomRight, WorldBottomRight); + + // Convert to minimap space. + FVector2D MinimapTopLeft = WorldToMinimap(WorldTopLeft); + FVector2D MinimapTopRight = WorldToMinimap(WorldTopRight); + FVector2D MinimapBottomLeft = WorldToMinimap(WorldBottomLeft); + FVector2D MinimapBottomRight = WorldToMinimap(WorldBottomRight); + + // Draw view frustum. + TArray Points; + + Points.Add(MinimapTopLeft); + Points.Add(MinimapTopRight); + Points.Add(MinimapBottomRight); + Points.Add(MinimapBottomLeft); + Points.Add(MinimapTopLeft); + + FSlateDrawElement::MakeLines( + InContext.OutDrawElements, + InContext.MaxLayer, + InContext.AllottedGeometry.ToPaintGeometry(), + Points); +} + +void URTSMinimapWidget::DrawBoxWithBrush(FPaintContext& InContext, const FVector2D& Position, const FSlateBrush& Brush) const +{ + FSlateDrawElement::MakeBox( + InContext.OutDrawElements, + InContext.MaxLayer, + InContext.AllottedGeometry.ToPaintGeometry(Position, Brush.ImageSize), + &Brush, + ESlateDrawEffect::None, + Brush.TintColor.GetSpecifiedColor()); +} + +FReply URTSMinimapWidget::HandleMinimapClick(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + ARTSPlayerController* Player = Cast(GetOwningPlayer()); + + if (!Player) + { + return FReply::Unhandled(); + } + + // Convert clicked minimap position to world space. + FVector2D MinimapPosition = InGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition()); + FVector WorldPosition = MinimapToWorld(MinimapPosition); + + // Notify player. + Player->NotifyOnMinimapClicked(InMouseEvent, MinimapPosition, WorldPosition); + return FReply::Handled(); +} + +FVector URTSMinimapWidget::MinimapToWorld(const FVector2D& MinimapPosition) const +{ + // Convert to relative minimap position. + float RelativeMinimapX = MinimapPosition.X / MinimapBackground.ImageSize.X; + float RelativeMinimapY = MinimapPosition.Y / MinimapBackground.ImageSize.Y; + + // Rotate to match UI coordinate system. + float Temp = RelativeMinimapX; + RelativeMinimapX = 1 - RelativeMinimapY; + RelativeMinimapY = Temp; + + // Convert to world coordinates. + float WorldX = (RelativeMinimapX - 0.5) * MinimapWorldSize.X; + float WorldY = (RelativeMinimapY - 0.5) * MinimapWorldSize.Y; + + return FVector(WorldX, WorldY, 0.0f); +} + +bool URTSMinimapWidget::ViewportToWorld(ARTSPlayerController* Player, const FVector2D& ViewportPosition, FVector& OutWorldPosition) const +{ + // Get ray. + FVector WorldOrigin; + FVector WorldDirection; + if (!UGameplayStatics::DeprojectScreenToWorld(Player, ViewportPosition, WorldOrigin, WorldDirection)) + { + return false; + } + + // Make plane. + FPlane ZPlane = FPlane(FVector::ZeroVector, FVector::UpVector); + + // Calculate intersection point. + OutWorldPosition = FMath::LinePlaneIntersection(WorldOrigin, WorldOrigin + WorldDirection * 1000.0f, ZPlane); + return true; +} + +FVector2D URTSMinimapWidget::WorldToMinimap(const FVector& WorldPosition) const +{ + // Get relative world position. + float RelativeWorldX = WorldPosition.X / MinimapWorldSize.X + 0.5f; + float RelativeWorldY = WorldPosition.Y / MinimapWorldSize.Y + 0.5f; + + // Rotate to match UI coordinate system. + float Temp = RelativeWorldX; + RelativeWorldX = RelativeWorldY; + RelativeWorldY = 1 - Temp; + + // Convert to minimap coordinates. + float MinimapX = RelativeWorldX * MinimapBackground.ImageSize.X; + float MinimapY = RelativeWorldY * MinimapBackground.ImageSize.Y; + + return FVector2D(MinimapX, MinimapY); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSFogOfWarActor.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSFogOfWarActor.cpp new file mode 100644 index 00000000..79a6fde4 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSFogOfWarActor.cpp @@ -0,0 +1,129 @@ +#include "Vision/RTSFogOfWarActor.h" + +#include "EngineUtils.h" +#include "RHI.h" +#include "Engine/PostProcessVolume.h" +#include "Engine/Texture2D.h" +#include "Materials/MaterialInterface.h" +#include "Materials/MaterialInstanceDynamic.h" + +#include "RTSLog.h" +#include "Vision/RTSVisionInfo.h" +#include "Vision/RTSVisionVolume.h" + + +ARTSFogOfWarActor::ARTSFogOfWarActor(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + PrimaryActorTick.bCanEverTick = true; +} + +void ARTSFogOfWarActor::Initialize(ARTSVisionVolume* InVisionVolume) +{ + // Get vision size. + VisionVolume = InVisionVolume; + + if (!VisionVolume) + { + UE_LOG(LogRTS, Warning, TEXT("No vision volume found, won't update fog of war.")); + return; + } + + if (!FogOfWarVolume) + { + UE_LOG(LogRTS, Warning, TEXT("No fog of war volume found, won't render fog of war.")); + return; + } + + // Setup fog of war buffer. + int32 SizeInTiles = VisionVolume->GetSizeInTiles(); + FVector SizeInWorld = VisionVolume->GetSizeInWorld(); + + FogOfWarTextureBuffer = new uint8[SizeInTiles * SizeInTiles * 4]; + + // Setup fog of war texture. + FogOfWarTexture = UTexture2D::CreateTransient(SizeInTiles, SizeInTiles); + +#if WITH_EDITORONLY_DATA + FogOfWarTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; +#endif + + FogOfWarTexture->AddToRoot(); + + FogOfWarTexture->UpdateResource(); + + FogOfWarUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, SizeInTiles, SizeInTiles); + + // Setup fog of war material. + FogOfWarMaterialInstance = UMaterialInstanceDynamic::Create(FogOfWarMaterial, nullptr); + FogOfWarMaterialInstance->SetTextureParameterValue(FName("VisibilityMask"), FogOfWarTexture); + FogOfWarMaterialInstance->SetScalarParameterValue(FName("OneOverWorldSize"), 1.0f / SizeInWorld.X); + FogOfWarMaterialInstance->SetScalarParameterValue(FName("OneOverTileSize"), 1.0f / SizeInTiles); + + // Setup fog of war post-process volume. + FogOfWarVolume->AddOrUpdateBlendable(FogOfWarMaterialInstance); + + UE_LOG(LogRTS, Log, TEXT("Set up %s with %s."), *GetName(), *VisionVolume->GetName()); +} + +void ARTSFogOfWarActor::Tick(float DeltaTime) +{ + AActor::Tick(DeltaTime); + + // Update texture. + if (!VisionVolume || !FogOfWarVolume || !VisionInfo) + { + return; + } + + int32 SizeInTiles = VisionVolume->GetSizeInTiles(); + + for (int32 Y = 0; Y < SizeInTiles; ++Y) + { + for (int32 X = 0; X < SizeInTiles; ++X) + { + const int i = Y * SizeInTiles + X; + + const int iBlue = i * 4 + 0; + const int iGreen = i * 4 + 1; + const int iRed = i * 4 + 2; + const int iAlpha = i * 4 + 3; + + switch (VisionInfo->GetVision(X, Y)) + { + case ERTSVisionState::VISION_Visible: + FogOfWarTextureBuffer[iBlue] = 0; + FogOfWarTextureBuffer[iGreen] = 0; + FogOfWarTextureBuffer[iRed] = 255; + FogOfWarTextureBuffer[iAlpha] = 0; + break; + + case ERTSVisionState::VISION_Known: + FogOfWarTextureBuffer[iBlue] = 0; + FogOfWarTextureBuffer[iGreen] = 255; + FogOfWarTextureBuffer[iRed] = 0; + FogOfWarTextureBuffer[iAlpha] = 0; + break; + + case ERTSVisionState::VISION_Unknown: + FogOfWarTextureBuffer[iBlue] = 0; + FogOfWarTextureBuffer[iGreen] = 0; + FogOfWarTextureBuffer[iRed] = 0; + FogOfWarTextureBuffer[iAlpha] = 0; + break; + } + } + } + + FogOfWarTexture->UpdateTextureRegions(0, 1, FogOfWarUpdateTextureRegion, SizeInTiles * 4, (uint32)4, FogOfWarTextureBuffer); +} + +UTexture2D* ARTSFogOfWarActor::GetFogOfWarTexture() const +{ + return FogOfWarTexture; +} + +void ARTSFogOfWarActor::SetupVisionInfo(ARTSVisionInfo* InVisionInfo) +{ + VisionInfo = InVisionInfo; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionComponent.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionComponent.cpp new file mode 100644 index 00000000..b9f8ab78 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionComponent.cpp @@ -0,0 +1,14 @@ +#include "Vision/RTSVisionComponent.h" + + +URTSVisionComponent::URTSVisionComponent(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + SightRadius = 1000.0f; +} + +float URTSVisionComponent::GetSightRadius() const +{ + return SightRadius; +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionInfo.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionInfo.cpp new file mode 100644 index 00000000..735a7c5d --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionInfo.cpp @@ -0,0 +1,237 @@ +#include "Vision/RTSVisionInfo.h" + +#include "EngineUtils.h" +#include "Net/UnrealNetwork.h" + +#include "RTSLog.h" +#include "RTSOwnerComponent.h" +#include "RTSPlayerController.h" +#include "RTSPlayerState.h" +#include "RTSTeamInfo.h" +#include "Vision/RTSVisionComponent.h" +#include "Vision/RTSVisionVolume.h" + + +ARTSVisionInfo::ARTSVisionInfo(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Enable replication. + SetReplicates(true); + bAlwaysRelevant = true; + NetUpdateFrequency = 1.0f; + + // Force ReceivedTeamIndex() on clients. + TeamIndex = 255; + + PrimaryActorTick.bCanEverTick = true; +} + +void ARTSVisionInfo::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(ARTSVisionInfo, TeamIndex, COND_InitialOnly); +} + +void ARTSVisionInfo::Initialize(ARTSVisionVolume* InVisionVolume) +{ + VisionVolume = InVisionVolume; + + if (!VisionVolume) + { + UE_LOG(LogRTS, Warning, TEXT("No vision volume found, won't update vision.")); + return; + } + + int32 SizeInTiles = VisionVolume->GetSizeInTiles(); + Tiles.SetNumZeroed(SizeInTiles * SizeInTiles); + + for (int32 Index = 0; Index < Tiles.Num(); ++Index) + { + Tiles[Index] = VisionVolume->GetMinimumVisionState(); + } + + UE_LOG(LogRTS, Log, TEXT("Set up %s with %s."), *GetName(), *VisionVolume->GetName()); +} + +void ARTSVisionInfo::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + if (!VisionVolume) + { + return; + } + + // Reset tiles. + int32 SizeInTiles = VisionVolume->GetSizeInTiles(); + Tiles.SetNumZeroed(SizeInTiles * SizeInTiles); + + const ERTSVisionState VisionAfterVisible = + FMath::Max(ERTSVisionState::VISION_Known, VisionVolume->GetMinimumVisionState()); + + for (int32 Index = 0; Index < Tiles.Num(); ++Index) + { + if (Tiles[Index] == ERTSVisionState::VISION_Visible) + { + Tiles[Index] = VisionAfterVisible; + } + } + + // Apply vision. + for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + + // Verify vision. + URTSVisionComponent* VisionComponent = Actor->FindComponentByClass(); + + if (!VisionComponent) + { + continue; + } + + // Verify team. + URTSOwnerComponent* OwnerComponent = Actor->FindComponentByClass(); + ARTSPlayerState* PlayerOwner = OwnerComponent->GetPlayerOwner(); + + if (!PlayerOwner) + { + continue; + } + + if (!PlayerOwner->GetTeam()) + { + continue; + } + + if (PlayerOwner->GetTeam()->GetTeamIndex() != TeamIndex) + { + continue; + } + + // Convert location and sight radius to tile space. + FVector ActorLocationWorld = Actor->GetActorLocation(); + FIntVector ActorLocationTile = VisionVolume->WorldToTile(ActorLocationWorld); + int32 ActorSightRadiusTile = FMath::FloorToInt(VisionComponent->GetSightRadius() / VisionVolume->GetTileSize()); + + /*UE_LOG(LogRTS, Log, TEXT("ActorLocationWorld: %s"), *ActorLocationWorld.ToString()); + UE_LOG(LogRTS, Log, TEXT("ActorLocationTile: %s"), *ActorLocationTile.ToString()); + UE_LOG(LogRTS, Log, TEXT("VisionComponent->SightRadius: %f"), VisionComponent->SightRadius); + UE_LOG(LogRTS, Log, TEXT("VisionVolume->SizePerTile: %f"), VisionVolume->SizePerTile); + UE_LOG(LogRTS, Log, TEXT("ActorSightRadiusTile: %i"), ActorSightRadiusTile);*/ + + // XXX VERY simple circle algorithm. + for (int32 RadiusY = -ActorSightRadiusTile; RadiusY <= ActorSightRadiusTile; RadiusY++) + { + for (int32 RadiusX = -ActorSightRadiusTile; RadiusX <= ActorSightRadiusTile; RadiusX++) + { + int32 TileX = ActorLocationTile.X + RadiusX; + int32 TileY = ActorLocationTile.Y + RadiusY; + + // Check if within circle. + if (TileX >= 0 && + TileY >= 0 && + TileX < SizeInTiles && + TileY < SizeInTiles && + (RadiusX * RadiusX + RadiusY * RadiusY < ActorSightRadiusTile * ActorSightRadiusTile)) + { + int32 TileIndex = GetTileIndex(TileX, TileY); + Tiles[TileIndex] = ERTSVisionState::VISION_Visible; + + //UE_LOG(LogRTS, Log, TEXT("Revealed tile (%i, %i)."), TileX, TileY); + } + } + } + } +} + +uint8 ARTSVisionInfo::GetTeamIndex() const +{ + return TeamIndex; +} + +void ARTSVisionInfo::SetTeamIndex(uint8 NewTeamIndex) +{ + TeamIndex = NewTeamIndex; + NotifyPlayerVisionInfoAvailable(); +} + +ERTSVisionState ARTSVisionInfo::GetVision(int32 X, int32 Y) const +{ + if (!VisionVolume) + { + return ERTSVisionState::VISION_Unknown; + } + + int32 TileIndex = GetTileIndex(X, Y); + return Tiles[TileIndex]; +} + +ARTSVisionInfo* ARTSVisionInfo::GetVisionInfoForTeam(UObject* WorldContextObject, uint8 InTeamIndex) +{ + UWorld* World = WorldContextObject->GetWorld(); + + for (TActorIterator It(World); It; ++It) + { + ARTSVisionInfo* VisionInfo = *It; + + if (VisionInfo->TeamIndex == InTeamIndex) + { + return VisionInfo; + } + } + + return nullptr; +} + +bool ARTSVisionInfo::GetTileCoordinates(int Index, int* OutX, int* OutY) const +{ + if (Index < 0 || Index >= Tiles.Num()) + { + return false; + } + + int32 SizeInTiles = VisionVolume->GetSizeInTiles(); + + *OutX = Index % SizeInTiles; + *OutY = Index / SizeInTiles; + return true; +} + +int32 ARTSVisionInfo::GetTileIndex(int X, int Y) const +{ + return Y * VisionVolume->GetSizeInTiles() + X; +} + +void ARTSVisionInfo::NotifyPlayerVisionInfoAvailable() +{ + // Notify local player. + UWorld* World = GetWorld(); + + if (!World) + { + return; + } + + ARTSPlayerController* Player = Cast(World->GetFirstPlayerController()); + + if (!Player) + { + return; + } + + ARTSTeamInfo* Team = Player->GetTeamInfo(); + + if (!Team || Team->GetTeamIndex() != TeamIndex) + { + return; + } + + Player->NotifyOnVisionInfoAvailable(this); +} + +void ARTSVisionInfo::ReceivedTeamIndex() +{ + NotifyPlayerVisionInfoAvailable(); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionVolume.cpp b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionVolume.cpp new file mode 100644 index 00000000..a9cbbb14 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Private/Vision/RTSVisionVolume.cpp @@ -0,0 +1,60 @@ +#include "Vision/RTSVisionVolume.h" + +#include "Components/BrushComponent.h" + +#include "RTSLog.h" + + +ARTSVisionVolume::ARTSVisionVolume(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // Set reasonable default values. + SizeInTiles = 256; +} + +void ARTSVisionVolume::Initialize() +{ + // Get vision world size. + UBrushComponent* VisionBrushComponent = GetBrushComponent(); + FBoxSphereBounds VisionBounds = VisionBrushComponent->CalcBounds(VisionBrushComponent->GetComponentTransform()); + + SizeInWorld = VisionBounds.BoxExtent * 2; + + // Calculate tile size. + TileSize = SizeInWorld.X / SizeInTiles; + + UE_LOG(LogRTS, Log, TEXT("%s has %i tiles of world size %f."), *GetName(), SizeInTiles, TileSize); +} + +int32 ARTSVisionVolume::GetSizeInTiles() const +{ + return SizeInTiles; +} + +FVector ARTSVisionVolume::GetSizeInWorld() const +{ + return SizeInWorld; +} + +float ARTSVisionVolume::GetTileSize() const +{ + return TileSize; +} + +ERTSVisionState ARTSVisionVolume::GetMinimumVisionState() const +{ + return MinimumVisionState; +} + +FIntVector ARTSVisionVolume::WorldToTile(const FVector& WorldPosition) const +{ + // Get relative world position. + float RelativeWorldX = WorldPosition.X / SizeInWorld.X + 0.5f; + float RelativeWorldY = WorldPosition.Y / SizeInWorld.Y + 0.5f; + + // Convert to minimap coordinates. + int32 TileX = FMath::FloorToInt(RelativeWorldX * SizeInTiles); + int32 TileY = FMath::FloorToInt(RelativeWorldY * SizeInTiles); + + return FIntVector(TileX, TileY, 0); +} diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Public/IRealTimeStrategy.h b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Public/IRealTimeStrategy.h new file mode 100644 index 00000000..41ebfc97 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/Public/IRealTimeStrategy.h @@ -0,0 +1,31 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +class IRealTimeStrategy : public IModuleInterface +{ +public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IRealTimeStrategy& Get() + { + return FModuleManager::LoadModuleChecked< IRealTimeStrategy >( "RealTimeStrategy" ); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "RealTimeStrategy" ); + } +}; diff --git a/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/RealTimeStrategy.Build.cs b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/RealTimeStrategy.Build.cs new file mode 100644 index 00000000..b17ccd64 --- /dev/null +++ b/Source/RTSProject/Plugins/RealTimeStrategy/Source/RealTimeStrategy/RealTimeStrategy.Build.cs @@ -0,0 +1,35 @@ +namespace UnrealBuildTool.Rules +{ + public class RealTimeStrategy : ModuleRules + { + public RealTimeStrategy(ReadOnlyTargetRules Target) + : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + bEnforceIWYU = true; + + PrivateIncludePaths.AddRange( + new string[] + { + "RealTimeStrategy/Private" + }); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "AIModule", + "UMG", + "Slate", + "SlateCore", + "RHI", + "RenderCore", + "Landscape", + "GameplayTags" + }); + } + } +} diff --git a/Source/RTSProject/RTSProject.uproject b/Source/RTSProject/RTSProject.uproject new file mode 100644 index 00000000..6d935bd1 --- /dev/null +++ b/Source/RTSProject/RTSProject.uproject @@ -0,0 +1,19 @@ +{ + "FileVersion": 3, + "EngineAssociation": "4.25", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "RTSProject", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "DaedalicTestAutomationPlugin", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Source/RTSProject/Source/RTSProject.Target.cs b/Source/RTSProject/Source/RTSProject.Target.cs new file mode 100644 index 00000000..413d6b96 --- /dev/null +++ b/Source/RTSProject/Source/RTSProject.Target.cs @@ -0,0 +1,19 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class RTSProjectTarget : TargetRules +{ + public RTSProjectTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + + bUseUnityBuild = false; + bUsePCHFiles = false; + + ExtraModuleNames.AddRange( new string[] { "RTSProject" } ); + + DefaultBuildSettings = BuildSettingsVersion.V2; + } +} diff --git a/Source/RTSProject/Source/RTSProject/RTSProject.Build.cs b/Source/RTSProject/Source/RTSProject/RTSProject.Build.cs new file mode 100644 index 00000000..7c326ed5 --- /dev/null +++ b/Source/RTSProject/Source/RTSProject/RTSProject.Build.cs @@ -0,0 +1,23 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; + +public class RTSProject : ModuleRules +{ + public RTSProject(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/Source/RTSProject/Source/RTSProject/RTSProject.cpp b/Source/RTSProject/Source/RTSProject/RTSProject.cpp new file mode 100644 index 00000000..f9889021 --- /dev/null +++ b/Source/RTSProject/Source/RTSProject/RTSProject.cpp @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "RTSProject.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, RTSProject, "RTSProject" ); diff --git a/Source/RTSProject/Source/RTSProject/RTSProject.h b/Source/RTSProject/Source/RTSProject/RTSProject.h new file mode 100644 index 00000000..90aad9e7 --- /dev/null +++ b/Source/RTSProject/Source/RTSProject/RTSProject.h @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/Source/RTSProject/Source/RTSProjectEditor.Target.cs b/Source/RTSProject/Source/RTSProjectEditor.Target.cs new file mode 100644 index 00000000..7899d1bf --- /dev/null +++ b/Source/RTSProject/Source/RTSProjectEditor.Target.cs @@ -0,0 +1,19 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class RTSProjectEditorTarget : TargetRules +{ + public RTSProjectEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + + bUseUnityBuild = false; + bUsePCHFiles = false; + + ExtraModuleNames.AddRange( new string[] { "RTSProject" } ); + + DefaultBuildSettings = BuildSettingsVersion.V2; + } +}