diff --git a/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml b/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml index 8eee5e43c..4d1d47022 100644 --- a/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml +++ b/.idea/.idea.Aetheria-Economy/.idea/contentModel.xml @@ -1279,6 +1279,7 @@ + diff --git a/Assets/Scripts/ServerShared/Agents/AgentTask.cs b/Assets/Scripts/ServerShared/Agents/AgentTask.cs new file mode 100644 index 000000000..cdb83ca5c --- /dev/null +++ b/Assets/Scripts/ServerShared/Agents/AgentTask.cs @@ -0,0 +1,17 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public abstract class AgentTask +{ + +} + +public enum AgentJob +{ + Mine, + Haul, + Tow, + Defend, + Attack +} diff --git a/Assets/Scripts/ServerShared/Agents/AgentTask.cs.meta b/Assets/Scripts/ServerShared/Agents/AgentTask.cs.meta new file mode 100644 index 000000000..1e3133080 --- /dev/null +++ b/Assets/Scripts/ServerShared/Agents/AgentTask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 610ba26a0be594144b79c6206a0623ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/Agents/VelocityMatch.cs b/Assets/Scripts/ServerShared/Agents/VelocityMatch.cs index a948c10a5..8581aa6f1 100644 --- a/Assets/Scripts/ServerShared/Agents/VelocityMatch.cs +++ b/Assets/Scripts/ServerShared/Agents/VelocityMatch.cs @@ -43,11 +43,11 @@ public class VelocityMatch : AgentBehavior var velocity = length(_entity.Velocity); var deltaV = Objective.Velocity - _entity.Velocity; - var stoppingTime = length(deltaV) / _entity.Context.Evaluate((_thrust.Data as ThrusterData).Thrust, _thrust.Item, _entity) * _entity.Mass; - var stoppingDistance = stoppingTime * velocity / 2; + var stoppingTime = length(deltaV) / (_entity.Context.Evaluate((_thrust.Data as ThrusterData).Thrust, _thrust.Item, _entity) / _entity.Mass); + var stoppingDistance = stoppingTime * (velocity / 2); var angleDiff = _entity.Direction.AngleDiff(deltaV); - var turnaroundTime = angleDiff / _entity.Context.Evaluate((_turning.Data as TurningData).Torque, _turning.Item, _entity) * _entity.Mass; + var turnaroundTime = angleDiff / (_entity.Context.Evaluate((_turning.Data as TurningData).Torque, _turning.Item, _entity) / _entity.Mass); var turnaroundDistance = turnaroundTime * velocity; return float2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime); diff --git a/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs b/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs index 6ea977562..4b83891cb 100644 --- a/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs +++ b/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs @@ -15,6 +15,7 @@ public class VelocityLimitData : IBehaviorData } } +[UpdateOrder(100)] public class VelocityLimit : IBehavior { public Entity Entity { get; } diff --git a/Assets/Scripts/StrategyGameManager.cs b/Assets/Scripts/StrategyGameManager.cs index 6548529c3..e12356df1 100644 --- a/Assets/Scripts/StrategyGameManager.cs +++ b/Assets/Scripts/StrategyGameManager.cs @@ -148,9 +148,17 @@ public class StrategyGameManager : MonoBehaviour void spawn(string[] args) { - Debug.Log("Spawning ship!"); + if (!TestMode) + return; + var zoneData = _cache.Get(_populatedZone); + if (zoneData == null) + { + Debug.Log("Attempted to spawn ship but no zone is populated!"); + return; + } + // Parse first argument as hull name, default to Fighter if argument missing HullData hullData; if (args.Length > 1) @@ -213,15 +221,75 @@ public class StrategyGameManager : MonoBehaviour } } + void newzone(string[] args) + { + var zoneData = _cache.Get(_selectedZone); + + if (zoneData == null) + { + Debug.Log("Attempted to regenerate zone but no zone is selected!"); + return; + } + + if(_populatedZone!=Guid.Empty) + _context.UnloadZone(_cache.Get(_populatedZone)); + _cache.Delete(zoneData); + _galaxyResponseZones.Remove(zoneData.ID); + + var newZone = zoneData.Copy(); + newZone.ID = Guid.NewGuid(); + newZone.Name = newZone.ID.ToString().Substring(0, 8); + newZone.Visited = false; + + var linkedZones = _cache.GetAll().Where(z => z.Wormholes.Any(w => w == zoneData.ID)).ToArray(); + foreach (var linkedZone in linkedZones) + { + linkedZone.Wormholes.Remove(zoneData.ID); + linkedZone.Wormholes.Add(newZone.ID); + } + newZone.Wormholes = linkedZones.Select(z => z.ID).ToList(); + + var zone = _galaxyResponseZones[newZone.ID] = new GalaxyResponseZone + { + Links = newZone.Wormholes.ToArray(), + Name = newZone.Name, + Position = newZone.Position, + ZoneID = newZone.ID + }; + + var galaxyZone = _galaxyZoneObjects[zoneData.ID]; + _galaxyZoneObjects.Remove(zoneData.ID); + galaxyZone.Label.text = newZone.Name; + _galaxyZoneObjects[newZone.ID] = galaxyZone; + + var collider = galaxyZone.Background.GetComponent(); + collider.Clear(); + collider.OnClick += (_, pointer) => + { + if (_selectedGalaxyZone != null) + _selectedGalaxyZone.Background.material.SetColor("_TintColor", UnselectedColor); + if(_selectedZone != zone.ZoneID) + CultClient.Send(new ZoneRequestMessage{ZoneID = zone.ZoneID}); + _selectedZone = zone.ZoneID; + _selectedGalaxyZone = galaxyZone; + _selectedGalaxyZone.Background.material.SetColor("_TintColor", SelectedColor); + _selectedZone = zone.ZoneID; + + // If the user double clicks on a zone, switch to the zone tab + if(pointer.clickCount == 2) ZoneTabButton.OnPointerClick(pointer); + }; + + _cache.Add(newZone); + + _selectedZone = newZone.ID; + _populatedZone = Guid.Empty; + + if(_currentTab == ZoneTabButton) + PopulateZone(); + } + private void Update() { - if (TestMode) - { - if (Input.GetKeyDown(KeyCode.Space) && _currentTab == ZoneTabButton) - { - PopulateZone(); - } - } if (_context != null) { _context.Time = Time.time; @@ -255,6 +323,14 @@ public class StrategyGameManager : MonoBehaviour } } + private void OnApplicationQuit() + { + if (TestMode) + { + // TODO: Save game state on exit + } + } + void PopulateGalaxy() { _galaxyPopulated = true; diff --git a/Assets/Scripts/UI/ClickCatcher.cs b/Assets/Scripts/UI/ClickCatcher.cs index 6d8e4b0ac..3859d535a 100644 --- a/Assets/Scripts/UI/ClickCatcher.cs +++ b/Assets/Scripts/UI/ClickCatcher.cs @@ -31,14 +31,14 @@ public class ClickCatcher : MonoBehaviour, IPointerEnterHandler, IPointerExitHan public void OnPointerEnter(PointerEventData eventData) { - Debug.Log("Pointer Entered"); + //Debug.Log("Pointer Entered"); OnEnter?.Invoke(eventData); PointerIsInside = true; } public void OnPointerExit(PointerEventData eventData) { - Debug.Log("Pointer Exited"); + //Debug.Log("Pointer Exited"); OnExit?.Invoke(eventData); PointerIsInside = false; } diff --git a/Assets/Scripts/UI/ClickableCollider.cs b/Assets/Scripts/UI/ClickableCollider.cs index 55eb7b1ce..9901ff86d 100644 --- a/Assets/Scripts/UI/ClickableCollider.cs +++ b/Assets/Scripts/UI/ClickableCollider.cs @@ -10,6 +10,11 @@ public class ClickableCollider : MonoBehaviour public void Click(PointerEventData eventData) => OnClick?.Invoke(this, eventData); + public void Clear() + { + OnClick = null; + } + void Start() { var proto = GetComponent(); diff --git a/Assets/Scripts/UnityExtensions.cs b/Assets/Scripts/UnityExtensions.cs index fd01d3d9c..e7700bb41 100644 --- a/Assets/Scripts/UnityExtensions.cs +++ b/Assets/Scripts/UnityExtensions.cs @@ -29,8 +29,8 @@ public static class UnityExtensions //monoList.Add(); foreach (var component in monoList) { - try - { + // try + // { Type type = component.GetType(); MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | @@ -40,15 +40,15 @@ public static class UnityExtensions if(method!=null) method.Invoke(component, new[] { value }); - } - catch (Exception e) - { - //Re-create the Error thrown by the original SendMessage function - if (options == SendMessageOptions.RequireReceiver) - Debug.LogError("SendMessage " + methodName + " has no receiver!"); - - Debug.LogError(e.Message); - } + // } + // catch (Exception e) + // { + // //Re-create the Error thrown by the original SendMessage function + // if (options == SendMessageOptions.RequireReceiver) + // Debug.LogError("SendMessage " + methodName + " has no receiver!"); + // + // Debug.LogError(e.Message); + // } } } diff --git a/Economy.Shared.Unity.csproj b/Economy.Shared.Unity.csproj index d174682ea..e0977aec9 100644 --- a/Economy.Shared.Unity.csproj +++ b/Economy.Shared.Unity.csproj @@ -63,6 +63,7 @@ + diff --git a/README.md b/README.md index 249df9224..4651ad015 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ This repository is the home of Aetheria, an economy focused RTS MMO where you co - [Getting the Files](#Getting-the-Files) - [Choosing a Task](#Choosing-a-Task) - [Database Editor Tools](#Database-Editor-Tools) - - [Connecting to RethinkDB](#Connecting-to-RethinkDB) - - [Editing Items](#Editing-Items) + - [Connecting to RethinkDB](#Connecting-to-RethinkDB) + - [Editing Items](#Editing-Items) - [Testing Locally](#Testing-Locally) + - [Debug Console](#Debug-Console) 6. [Galaxy Editor](#Galaxy-Editor) - [Map Layer Data](#Map-Layer-Data) - [Star Tools](#Star-Tools) @@ -29,7 +30,7 @@ This repository is the home of Aetheria, an economy focused RTS MMO where you co ## Game Design -The ARPG game design document is available [here](https://docs.google.com/document/d/1iULu1WsbuQoUM3c87XkGseb1P-8R5xlruoiyg03TsSE/edit?usp=sharing), while the RTS gameplay is documented [here](https://docs.google.com/document/d/1U3uGFqQboAiFJ_Y-nUOGpyixbXUHRbc5DiCuB59GM4w/edit?usp=sharing). There's also a document explaining how some of the shaders work [here](https://docs.google.com/document/d/1AFycvCtW6hA1jkKq1ZmYd3k6_uEWaaCqcZ4fYj4vU6A/edit?usp=sharing). The goal is to essentially create two games which both take place in the same persistent universe, allowing players with vastly different preferences to struggle together for the survival of mankind. Each instance of the game lasts until the inevitable destruction of the entire population at the hands of aliens, after which the universe resets. Each loop is designed to last up to a couple of months, during which the hostility of the aliens steadily increases until the players are unable to hold back the tide. +The ARPG game design document is available [here](https://docs.google.com/document/d/1iULu1WsbuQoUM3c87XkGseb1P-8R5xlruoiyg03TsSE/edit?usp=sharing), while the RTS gameplay is documented [here](https://docs.google.com/document/d/1U3uGFqQboAiFJ_Y-nUOGpyixbXUHRbc5DiCuB59GM4w/edit?usp=sharing). There's also a document explaining how some of the shaders work [here](https://docs.google.com/document/d/1AFycvCtW6hA1jkKq1ZmYd3k6_uEWaaCqcZ4fYj4vU6A/edit?usp=sharing). The goal is to essentially create two games which both take place in the same persistent universe, allowing players with vastly different preferences to struggle together for the survival of mankind. Each instance of the game lasts until the inevitable destruction of the entire population at the hands of aliens, after which the universe resets. Each loop is designed to last up to a couple of months, during which the hostility of the aliens steadily increases until the players are unable to hold back the tide. As players gain proficiency with the systems, the length of the time loop may increase, allowing us to organically inject new content into the timeline. ## Previous Work @@ -39,7 +40,7 @@ The concept for Aetheria goes back many years, during which I have steadily acqu As a result of lessons learned, the current focus is on the economy system, and building a client-server architecture for the networked simulation of a persistent universe. The goal is to create an RTS client, allowing players to take the role of a corporation, where they can define roles for their population, gather resources, build infrastructure, research new technology and produce items in order to make as much money as possible. -Once we have built a persistent universe with a dynamic economy, we will begin rebuilding the ARPG client allowing the player to take control of a single ship and engage in fast-paced combat, questing and trading. +Once we have built a persistent universe with a dynamic economy, we will begin rebuilding the ARPG client allowing the player to take control of a single ship and engage in fast-paced combat, exploration, trading and narrative questing. ## Architecture @@ -49,7 +50,7 @@ There are two solutions in this repository. One is a Unity project containing th ### Third Party Libraries -Client-Server communication is implemented using [LiteNetLib](https://github.com/RevenantX/LiteNetLib), a reliable UDP transport library which we use to transmit [MessagePack](https://github.com/neuecc/MessagePack-CSharp) over the wire. +Client-Server communication is implemented using [LiteNetLib](https://github.com/RevenantX/LiteNetLib), a semi-reliable UDP transport library which we use to transmit [MessagePack](https://github.com/neuecc/MessagePack-CSharp) over the wire. Aetheria uses [RethinkDB](https://rethinkdb.com/) for data persistence. To make this possible, all persistent data is marked with attributes for both MessagePack and [JSON.Net](https://www.newtonsoft.com/json) serialization. During operation, the client does not communicate with the database server directly, only the game server does that; the game server caches data relevant to the game and sends it to the clients. @@ -59,7 +60,7 @@ The codebase makes heavy use of C#'s [Language Integrated Queries (LINQ)](https: ### Data Structures -All of the state which is persisted to the database inherits from the DatabaseEntry class, which uses a GUID as each entry's primary key. Whenever a reference to a database entry must be held, it should be stored as a GUID and when needed, retrieved directly from the DatabaseCache held by the GameContext. References to Database Entries *should never be stored directly!* This is because the contents of entries can be updated in realtime by the database, causing changes to be pushed to the DatabaseCache and any previously existing DatabaseEntry instances will be outdated and invalid, potentially carrying stale data. +All of the state which is persisted to the database inherits from the DatabaseEntry class, which uses a GUID as each entry's primary key. Whenever a reference to a database entry must be held, it should be stored as a GUID and when needed, retrieved directly from the DatabaseCache held by the GameContext. References to Database Entries *should never be stored directly!* This is because the contents of entries can be updated in realtime by the database, causing changes to be pushed to the DatabaseCache and any previously existing DatabaseEntry instances will be outdated and invalid, potentially carrying stale data. #### Equipment @@ -87,13 +88,9 @@ When you have synced with the repository, you can open the project using Unity. ### Choosing a Task -We are organizing according to an [Agile development](https://en.wikipedia.org/wiki/Agile_software_development) schedule, with the progress of each sprint being tracked on its own board in the [Github Projects tab](https://github.com/rwvens/Aetheria-Economy/projects). If you wish to take on a task from the board, please contact us to become an official contributor so that the task can be assigned to you directly. Some issues are not on the sprint schedule, those are ideal for developers who want to jump in but are shy about joining. +We are organizing according to an [Agile development](https://en.wikipedia.org/wiki/Agile_software_development) schedule, with the progress of each sprint being tracked on its own board in the [Github Projects tab](https://github.com/rwvens/Aetheria-Economy/projects). If you wish to take on a task from the board, please contact us to become an official contributor so that the task can be assigned to you directly. Some issues are not on the sprint schedule, those are ideal for developers who want to jump in but are shy about joining. We use the [good first issue](https://github.com/rwvens/Aetheria-Economy/labels/good%20first%20issue) label for issues that don't require heavy knowledge of the codebase. -### Testing Locally - -If midgard.gamecult.games is not currently up, you can always build and run the economy server yourself, and connect to it by typing localhost into the main menu. To start the game in this mode you open the "Start" scene and type "localhost" into the host field. You will still need to create an account. - -Another way to test the game entirely offline doesn't require running the economy server, but you still need to download the database contents. Instead of connecting to the database with the "Connect" button, click "Connect All", which in addition to syncing item data will also sync galaxy data such as the sectors. Once the tools are finished syncing, which can take a while (you know it's done when the console has stopped logging "Received {table} entry"), you can click "Save" to create a local backup of the entire database contents. If you enter Play mode in the "Main" scene with the TestMode parameter set to true in the game manager, the game will use that local copy instead of requiring a connection to the master server. +You don't have to be a programmer to contribute, either! We have issue labels for and very much welcome contributions from [writers](https://github.com/rwvens/Aetheria-Economy/labels/worldbuilding) and [game designers](https://github.com/rwvens/Aetheria-Economy/labels/game%20design). ### Database Editor Tools @@ -107,6 +104,12 @@ At the top of the list view there is a text field where you can enter the URL of You can unfold the categories of items in the list view to see what items exist. If you select an item, the Database Inspector will populate with all of the available fields of that item. Any changes you make in the Inspector will automatically be pushed to RethinkDB. If you've connected to the production database, this will update the stats of in-game items in real-time! +### Testing Locally + +If midgard.gamecult.games is not currently up, you can always build and run the economy server yourself, and connect to it by typing localhost into the main menu. To start the game in this mode you open the "Start" scene and type "localhost" into the host field. You will still need to create an account. + +Another way to test the game entirely offline doesn't require running the economy server, but you still need to download the database contents. Instead of connecting to the database with the "Connect" button, click "Connect All", which in addition to syncing item data will also sync galaxy data such as the sectors. Once the tools are finished syncing, which can take a while (you know it's done when the console has stopped logging "Received {table} entry"), you can click "Save" to create a local backup of the entire database contents. If you enter Play mode in the "Main" scene with the TestMode parameter set to true in the game manager, the game will use that local copy instead of requiring a connection to the master server. + ### Debug Console Pressing the tilde key (`) while running the game allows you to access the console. Here you can view the debug log as well as entering commands which aid in testing various game mechanics. @@ -115,12 +118,29 @@ Pressing the tilde key (`) while running the game allows you to access the conso ##### spawn +Creates a new entity under your control + +Prerequisites: + +- Sector is selected +- Sector is populated (Sector tab is open) + Arguments: - Entity type: needs to be "ship", "station" or "turret" - Hull name (optional): selects the hull of the spawned entity by name - Gear List (optional): every additional argument specifies a Gear item to install +##### newzone + +Replaces the current zone with a new one, randomizing the ID and contents + +Prerequisites: + +- Sector is selected + +Arguments: None + ### Galaxy Editor When you select a galaxy asset in the Unity scene hierarchy, a custom editor opens in the inspector which enables the procedural generation of a new galaxy. There you can find some variables pertaining to the galaxy as a whole, such as the number and twist of the spiral arms. diff --git a/RethinkDB Queries.txt b/RethinkDB Queries.txt index 8c32afb78..364d2e943 100644 --- a/RethinkDB Queries.txt +++ b/RethinkDB Queries.txt @@ -3,4 +3,7 @@ r.db('Aetheria').table('Galaxy').filter({$type:'ZoneData'}).update({visited: fal r.db('Aetheria').table('Galaxy').filter({$type:'ZoneData'}).update({orbits: [ ]}) r.db('Aetheria').table('Galaxy').filter({$type:'ZoneData'}).update({planets: [ ]}) r.db('Aetheria').table('Galaxy').filter({$type:'OrbitData'}).delete() -r.db('Aetheria').table('Galaxy').filter({$type:'PlanetData'}).delete() \ No newline at end of file +r.db('Aetheria').table('Galaxy').filter({$type:'PlanetData'}).delete() + +Find Unassigned Items +r.db('Aetheria').table('Items').filter({mass:0}).pluck('$type','name') \ No newline at end of file