From 6cefa618af3857f0d685c1433f55c2edcefe49b9 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 22 Jun 2019 22:17:31 +0100 Subject: [PATCH] [Simantics, Server] Substantial Backend Improvements - Fix a bug where the game could crash when switching properties. - Global Tuning: Tuning that is sent as soon as the player logs in, and is available in city view. - Tuning Presets: Tuning that can be applied as part of an ingame timed event. This can include tuning for motives, object payouts, skill speed and more - and allows all of these things to be automatically changed while the server is running. - Admin API for Events. (should make things easier on other M.O.M.I. ;) ) - Tuning changes while the server is running now immediately update any lots that are open. (fixes dynamic payouts not updating til a lot restart) - Interpolation for slot-slot movement. Very simple right now - could be improved for things like table slots. - Hidden objects (like the go here destination) no longer update shadows, causing a stutter. - New collision test function - should be helpful finding the desyncs that have been hitting popular lots. - Fix object shadows when shadows for surrounding lots are enabled - Terrain Force logic now uses global tuning. - "Community" category flag can now be set by Volcanic. --- .../ResourceBrowser/OBJDEditor.Designer.cs | 13 ++ .../FSO.IDE/ResourceBrowser/OBJDEditor.cs | 1 + .../Admin/AdminEventsController.cs | 159 ++++++++++++++++++ .../Models/EventCreateModel.cs | 38 +++++ .../Services/GithubUpdateUploader.cs | 12 +- TSOClient/FSO.Server.Clients/AriesClient.cs | 54 +++++- .../DA/DbEvents/DbEvent.cs | 6 + .../DA/DbEvents/IEvents.cs | 4 +- .../DA/DbEvents/SqlEvents.cs | 13 +- .../FSO.Server.Database/DA/Lots/SqlLots.cs | 2 +- .../DA/Tuning/DbTuningPreset.cs | 26 +++ .../FSO.Server.Database/DA/Tuning/ITuning.cs | 10 ++ .../DA/Tuning/SqlTuning.cs | 49 ++++++ .../changes/0027_tuning_presets.sql | 23 +++ .../DatabaseScripts/manifest.json | 8 + .../FSO.Server.Database.csproj | 4 + .../Electron/ElectronPacketType.cs | 1 + .../Electron/ElectronPackets.cs | 3 +- .../Electron/Packets/GlobalTuningUpdate.cs | 49 ++++++ .../FSO.Server.Protocol.csproj | 2 + .../Gluon/GluonPacketType.cs | 6 + .../FSO.Server.Protocol/Gluon/GluonPackets.cs | 3 +- .../Gluon/Packets/TuningChanged.cs | 32 ++++ TSOClient/FSO.Server/FSO.Server.csproj | 1 + .../FSO.Server/Servers/City/CityServer.cs | 1 + .../Servers/City/Domain/EventSystem.cs | 47 +++++- .../Servers/City/Domain/LotServerPicker.cs | 13 ++ .../FSO.Server/Servers/City/Domain/Tuning.cs | 50 ++++++ .../VoltronConnectionLifecycleHandler.cs | 5 +- .../Servers/Lot/Domain/LotContainer.cs | 13 ++ .../FSO.Server/Servers/Lot/Domain/LotHost.cs | 18 ++ .../Lot/Handlers/LotNegotiationHandler.cs | 5 + .../Servers/Tasks/Domain/JobBalanceTask.cs | 12 +- TSOClient/FSO.Server/Utils/GluonHostPool.cs | 9 +- .../Regulators/CityConnectionRegulator.cs | 16 +- .../tso.client/Rendering/City/CityContent.cs | 38 +++-- TSOClient/tso.client/UI/Panels/UILotPage.cs | 2 +- TSOClient/tso.common/Model/DynamicTuning.cs | 6 + .../Patch/TSOPorts/clothingboothvac.piff | Bin 0 -> 1163 bytes .../Patch/TSOPorts/clothingboothvac.spf.piff | Bin 0 -> 15883 bytes .../Patch/TSOPorts/clothingboothvac.str.piff | Bin 0 -> 1150 bytes .../Content/Patch/TSOPorts/petawards.piff | Bin 0 -> 3263 bytes .../Content/Patch/TSOPorts/petawards.spf.piff | Bin 0 -> 54060 bytes .../Content/Patch/TSOPorts/petawards.str.piff | Bin 0 -> 2366 bytes .../Patch/TSOPorts/petplayfountain.piff | Bin 0 -> 2091 bytes .../Patch/TSOPorts/petplayfountain.spf.piff | Bin 0 -> 16017 bytes .../Patch/TSOPorts/petplayfountain.str.piff | Bin 0 -> 1386 bytes .../Content/Patch/destination.piff | Bin 0 -> 634 bytes .../Content/Patch/destination.str.piff | Bin 0 -> 379 bytes .../Content/Patch/lampceiling2.piff | Bin 0 -> 336 bytes .../Content/Patch/lampceiling2.spf.piff | Bin 0 -> 65584 bytes .../Content/Patch/personglobals.str.piff | Bin 0 -> 218 bytes .../Content/Patch/personglobals_wakeup.piff | Bin 348 -> 413 bytes .../NetPlay/Model/Commands/VMNetChatCmd.cs | 14 +- .../tso.simantics/Test/CollisionTestUtils.cs | 45 ++++- TSOClient/tso.simantics/VMContext.cs | 2 +- TSOClient/tso.simantics/engine/VMMemory.cs | 2 +- TSOClient/tso.simantics/entities/VMEntity.cs | 1 + .../tso.simantics/primitives/VMDropOnto.cs | 1 + TSOClient/tso.world/LMap/LMapBatch.cs | 1 + .../tso.world/components/EntityComponent.cs | 28 ++- .../tso.world/components/TerrainComponent.cs | 24 ++- TSOClient/tso.world/model/LotTilePos.cs | 5 + 63 files changed, 827 insertions(+), 50 deletions(-) create mode 100644 TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs create mode 100644 TSOClient/FSO.Server.Api.Core/Models/EventCreateModel.cs create mode 100644 TSOClient/FSO.Server.Database/DA/Tuning/DbTuningPreset.cs create mode 100644 TSOClient/FSO.Server.Database/DatabaseScripts/changes/0027_tuning_presets.sql create mode 100644 TSOClient/FSO.Server.Protocol/Electron/Packets/GlobalTuningUpdate.cs create mode 100644 TSOClient/FSO.Server.Protocol/Gluon/Packets/TuningChanged.cs create mode 100644 TSOClient/FSO.Server/Servers/City/Domain/Tuning.cs create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/clothingboothvac.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/clothingboothvac.spf.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/clothingboothvac.str.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/petawards.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/petawards.spf.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/petawards.str.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.spf.piff create mode 100644 TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.str.piff create mode 100644 TSOClient/tso.content/Content/Patch/destination.piff create mode 100644 TSOClient/tso.content/Content/Patch/destination.str.piff create mode 100644 TSOClient/tso.content/Content/Patch/lampceiling2.piff create mode 100644 TSOClient/tso.content/Content/Patch/lampceiling2.spf.piff create mode 100644 TSOClient/tso.content/Content/Patch/personglobals.str.piff diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs index e28e34c8c..01d74b414 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs @@ -123,6 +123,7 @@ private void InitializeComponent() this.NameLabel = new System.Windows.Forms.Label(); this.NameEntry = new System.Windows.Forms.TextBox(); this.GUIDButton = new System.Windows.Forms.Button(); + this.CatCommunity = new System.Windows.Forms.CheckBox(); this.ThumbnailBox.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ThumbnailPic)).BeginInit(); this.VisualBox.SuspendLayout(); @@ -662,6 +663,7 @@ private void InitializeComponent() // // CatalogBox // + this.CatalogBox.Controls.Add(this.CatCommunity); this.CatalogBox.Controls.Add(this.CatResidence); this.CatalogBox.Controls.Add(this.CatEntertainment); this.CatalogBox.Controls.Add(this.CatGames); @@ -1196,6 +1198,16 @@ private void InitializeComponent() this.GUIDButton.UseVisualStyleBackColor = true; this.GUIDButton.MouseDown += new System.Windows.Forms.MouseEventHandler(this.GUIDButton_MouseDown); // + // CatCommunity + // + this.CatCommunity.AutoSize = true; + this.CatCommunity.Location = new System.Drawing.Point(94, 107); + this.CatCommunity.Name = "CatCommunity"; + this.CatCommunity.Size = new System.Drawing.Size(134, 17); + this.CatCommunity.TabIndex = 47; + this.CatCommunity.Text = "Community (Town Hall)"; + this.CatCommunity.UseVisualStyleBackColor = true; + // // OBJDEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1354,5 +1366,6 @@ private void InitializeComponent() private System.Windows.Forms.TextBox NameEntry; private System.Windows.Forms.Button GUIDButton; private System.Windows.Forms.Button ThumbSave; + private System.Windows.Forms.CheckBox CatCommunity; } } diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.cs b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.cs index 58f53745c..52f8a9742 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.cs @@ -89,6 +89,7 @@ public void Init(GameObject obj, ObjectWindow master) { CatGames, new PropFlagCombo("LotCategories", 8) }, { CatEntertainment, new PropFlagCombo("LotCategories", 9) }, { CatResidence, new PropFlagCombo("LotCategories", 10) }, + { CatCommunity, new PropFlagCombo("LotCategories", 11) }, { SklCooking, new PropFlagCombo("RatingSkillFlags", 0) }, { SklMechanical, new PropFlagCombo("RatingSkillFlags", 1) }, { SklLogic, new PropFlagCombo("RatingSkillFlags", 2) }, diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs new file mode 100644 index 000000000..d0d78146e --- /dev/null +++ b/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using FSO.Server.Api.Core.Models; +using FSO.Server.Api.Core.Utils; +using FSO.Server.Database.DA.DbEvents; +using FSO.Server.Database.DA.Tuning; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; + +// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace FSO.Server.Api.Core.Controllers.Admin +{ + [EnableCors("AdminAppPolicy")] + [Route("admin/events")] + [ApiController] + public class AdminEventsController : ControllerBase + { + //List events + [HttpGet] + public IActionResult Get(int limit, int offset, string order) + { + if (limit == 0) limit = 20; + if (order == null) order = "start_day"; + var api = Api.INSTANCE; + api.DemandModerator(Request); + using (var da = api.DAFactory.Get()) + { + + if (limit > 100) + { + limit = 100; + } + + var result = da.Events.All((int)offset, (int)limit, order); + return ApiResponse.PagedList(Request, HttpStatusCode.OK, result); + } + } + + [HttpGet("presets")] + public IActionResult GetPresets() + { + var api = Api.INSTANCE; + api.DemandModerator(Request); + using (var da = api.DAFactory.Get()) + { + return new JsonResult(da.Tuning.GetAllPresets().ToList()); + } + } + + [HttpPost("presets")] + public IActionResult CreatePreset([FromBody]PresetCreateModel request) + { + var api = Api.INSTANCE; + api.DemandModerator(Request); + using (var da = api.DAFactory.Get()) + { + //make the preset first + var preset_id = da.Tuning.CreatePreset( + new DbTuningPreset() + { + name = request.name, + description = request.description, + flags = request.flags + }); + + foreach (var item in request.items) + { + da.Tuning.CreatePresetItem(new DbTuningPresetItem() + { + preset_id = preset_id, + tuning_type = item.tuning_type, + tuning_table = item.tuning_table, + tuning_index = item.tuning_index, + value = item.value + }); + } + return new JsonResult(da.Tuning.GetAllPresets().ToList()); + } + } + + [HttpGet("presets/{preset_id}")] + public IActionResult GetPresetEntries(int preset_id) + { + var api = Api.INSTANCE; + api.DemandModerator(Request); + using (var da = api.DAFactory.Get()) + { + return new JsonResult(da.Tuning.GetPresetItems(preset_id).ToList()); + } + } + + [HttpDelete("presets/{preset_id}")] + public IActionResult DeletePreset(int preset_id) + { + var api = Api.INSTANCE; + api.DemandModerator(Request); + using (var da = api.DAFactory.Get()) + { + return da.Tuning.DeletePreset(preset_id) ? (IActionResult)Ok() : NotFound(); + } + } + + // POST admin/updates (start update generation) + [HttpPost] + public IActionResult Post([FromBody]EventCreateModel request) + { + var api = Api.INSTANCE; + api.DemandModerator(Request); + + using (var da = api.DAFactory.Get()) + { + DbEventType type; + try + { + type = Enum.Parse(request.type); + } + catch + { + return BadRequest("Event type must be one of:" + string.Join(", ", Enum.GetNames(typeof(DbEventType)))); + } + var model = new DbEvent() + { + title = request.title, + description = request.description, + start_day = request.start_day, + end_day = request.end_day, + type = type, + value = request.value, + value2 = request.value2, + mail_subject = request.mail_subject, + mail_message = request.mail_message, + mail_sender = request.mail_sender, + mail_sender_name = request.mail_sender_name + }; + return new JsonResult(new { id = da.Events.Add(model) }); + } + } + + [HttpDelete] + [Route("{id}")] + public IActionResult Delete(int id) + { + var api = Api.INSTANCE; + api.DemandModerator(Request); + + using (var da = api.DAFactory.Get()) + { + if (!da.Events.Delete(id)) return NotFound(); + } + + return Ok(); + } + + } +} diff --git a/TSOClient/FSO.Server.Api.Core/Models/EventCreateModel.cs b/TSOClient/FSO.Server.Api.Core/Models/EventCreateModel.cs new file mode 100644 index 000000000..309ed868a --- /dev/null +++ b/TSOClient/FSO.Server.Api.Core/Models/EventCreateModel.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FSO.Server.Api.Core.Models +{ + public class EventCreateModel + { + public string title; + public string description; + public DateTime start_day; + public DateTime end_day; + public string type; + public int value; + public int value2; + public string mail_subject; + public string mail_message; + public int mail_sender; + public string mail_sender_name; + } + + public class PresetCreateModel + { + public string name; + public string description; + public int flags; + public List items; + } + + public class PresetItemModel + { + public string tuning_type; + public int tuning_table; + public int tuning_index; + public float value; + } +} diff --git a/TSOClient/FSO.Server.Api.Core/Services/GithubUpdateUploader.cs b/TSOClient/FSO.Server.Api.Core/Services/GithubUpdateUploader.cs index 3a3ff4a80..cd57c2f1a 100644 --- a/TSOClient/FSO.Server.Api.Core/Services/GithubUpdateUploader.cs +++ b/TSOClient/FSO.Server.Api.Core/Services/GithubUpdateUploader.cs @@ -47,8 +47,16 @@ public async Task UploadFile(string destPath, string fileName, string gr } using (var file = File.Open(fileName, System.IO.FileMode.Open, FileAccess.Read, FileShare.Read)) { - var asset = await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload(destPath, "application/zip", file, new TimeSpan(1, 0, 0))); - return asset.BrowserDownloadUrl; + try + { + var asset = await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload(destPath, "application/zip", file, null)); + return asset.BrowserDownloadUrl; + } catch (Exception e) + { + //last time i tried, it mysteriously failed here but the file was uploaded :thinking: + Console.WriteLine($"!! Upload request for {destPath} failed, check it actually succeeded !! \n" + e.ToString()); + return $"https://github.com/{Config.User}/{Config.Repository}/releases/download/{groupName}/{destPath}"; + } } } } diff --git a/TSOClient/FSO.Server.Clients/AriesClient.cs b/TSOClient/FSO.Server.Clients/AriesClient.cs index cfd59a59c..9dd471604 100644 --- a/TSOClient/FSO.Server.Clients/AriesClient.cs +++ b/TSOClient/FSO.Server.Clients/AriesClient.cs @@ -35,7 +35,40 @@ public interface IAriesEventSubscriber void InputClosed(AriesClient session); } + public class NullIOHandler : IoHandler + { + public void ExceptionCaught(IoSession session, Exception cause) + { + } + + public void InputClosed(IoSession session) + { + } + + public void MessageReceived(IoSession session, object message) + { + } + + public void MessageSent(IoSession session, object message) + { + } + + public void SessionClosed(IoSession session) + { + } + + public void SessionCreated(IoSession session) + { + } + + public void SessionIdle(IoSession session, IdleStatus status) + { + } + public void SessionOpened(IoSession session) + { + } + } public class AriesClient : IoHandler { @@ -98,11 +131,15 @@ public void Connect(IPEndPoint target) { if (Connector != null) { - //dispose our old connector, if one is present. - Connector.Dispose(); - Disconnect(); + //old connector might still be establishing connection... + //we need to stop that + Connector.Handler = new NullIOHandler(); //don't hmu + //we can't cancel a mina.net connector, but we can sure as hell ~~avenge it~~ stop it from firing events. + //if we tried to dispose it, we'd get random disposed object exceptions because mina doesn't expect you to cancel that early. + Disconnect(); //if we have already established a connection, make sure it is closed. } Connector = new AsyncSocketConnector(); + var connector = Connector; Connector.ConnectTimeoutInMillis = 10000; //Connector.FilterChain.AddLast("logging", new LoggingFilter()); @@ -112,7 +149,16 @@ public void Connect(IPEndPoint target) //Connector.FilterChain.AddFirst("ssl", ssl); Connector.FilterChain.AddLast("protocol", new ProtocolCodecFilter(new AriesProtocol(Kernel))); - var future = Connector.Connect(target, new Action(OnConnect)); + var future = Connector.Connect(target, (IoSession session, IConnectFuture future2) => + { + if (future2.Canceled || future2.Exception != null) + { + if (connector.Handler != null) SessionClosed(session); + } + + if (connector.Handler is NullIOHandler) session.Close(true); + else this.Session = session; + }); Task.Run(() => { diff --git a/TSOClient/FSO.Server.Database/DA/DbEvents/DbEvent.cs b/TSOClient/FSO.Server.Database/DA/DbEvents/DbEvent.cs index c92702959..a650e5df6 100644 --- a/TSOClient/FSO.Server.Database/DA/DbEvents/DbEvent.cs +++ b/TSOClient/FSO.Server.Database/DA/DbEvents/DbEvent.cs @@ -20,6 +20,12 @@ public class DbEvent public string mail_message { get; set; } public int? mail_sender { get; set; } public string mail_sender_name { get; set; } + + public string type_str { + get { + return type.ToString(); + } + } } public class DbEventParticipation diff --git a/TSOClient/FSO.Server.Database/DA/DbEvents/IEvents.cs b/TSOClient/FSO.Server.Database/DA/DbEvents/IEvents.cs index 939185a03..ca5124c7c 100644 --- a/TSOClient/FSO.Server.Database/DA/DbEvents/IEvents.cs +++ b/TSOClient/FSO.Server.Database/DA/DbEvents/IEvents.cs @@ -1,4 +1,5 @@ -using System; +using FSO.Server.Database.DA.Utils; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,6 +9,7 @@ namespace FSO.Server.Database.DA.DbEvents { public interface IEvents { + PagedList All(int offset = 0, int limit = 20, string orderBy = "start_day"); List GetActive(DateTime time); int Add(DbEvent evt); bool Delete(int event_id); diff --git a/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs b/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs index bf1c31b66..b6fb8c43b 100644 --- a/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs +++ b/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using FSO.Server.Database.DA.Utils; namespace FSO.Server.Database.DA.DbEvents { @@ -12,11 +13,19 @@ public class SqlEvents : AbstractSqlDA, IEvents public SqlEvents(ISqlContext context) : base(context){ } + public PagedList All(int offset = 1, int limit = 20, string orderBy = "start_day") + { + var connection = Context.Connection; + var total = connection.Query("SELECT COUNT(*) FROM fso_events").FirstOrDefault(); + var results = connection.Query("SELECT * FROM fso_events ORDER BY @order DESC LIMIT @offset, @limit", new { order = orderBy, offset = offset, limit = limit }); + return new PagedList(results, offset, total); + } + public int Add(DbEvent evt) { - var result = Context.Connection.Query("INSERT INTO fso_inbox (title, description, start_day, " + + var result = Context.Connection.Query("INSERT INTO fso_events (title, description, start_day, " + "end_day, type, value, value2, mail_subject, mail_message, mail_sender, mail_sender_name) " + - " VALUES (@title, @description, @start_day, @end_day, @type, @value, @value2, " + + " VALUES (@title, @description, @start_day, @end_day, @type_str, @value, @value2, " + " @mail_subject, @mail_message, @mail_sender, @mail_sender_name); SELECT LAST_INSERT_ID();", evt).First(); return result; } diff --git a/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs b/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs index bcfe8043a..ad416b76d 100644 --- a/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs +++ b/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs @@ -111,7 +111,7 @@ public List GetLocationsInNhood(uint nhood_id) public List GetCommunityLocations(int shard_id) { - return Context.Connection.Query("SELECT location FROM fso_lots WHERE shard_id = @shard_id AND category = 'community'", new { shard_id = shard_id }).ToList(); + return Context.Connection.Query("SELECT location FROM fso_lots WHERE shard_id = @shard_id AND (category = 'community' OR category = 'recent')", new { shard_id = shard_id }).ToList(); } public List AllNames(int shard_id) diff --git a/TSOClient/FSO.Server.Database/DA/Tuning/DbTuningPreset.cs b/TSOClient/FSO.Server.Database/DA/Tuning/DbTuningPreset.cs new file mode 100644 index 000000000..1625108c9 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/Tuning/DbTuningPreset.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSO.Server.Database.DA.Tuning +{ + public class DbTuningPreset + { + public int preset_id; + public string name; + public string description; + public int flags; + } + + public class DbTuningPresetItem + { + public int item_id; + public int preset_id; + public string tuning_type; + public int tuning_table; + public int tuning_index; + public float value; + } +} diff --git a/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs b/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs index c67fbec02..750d9ee0b 100644 --- a/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs +++ b/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs @@ -10,5 +10,15 @@ public interface ITuning { IEnumerable All(); IEnumerable AllCategory(string type, int table); + IEnumerable GetAllPresets(); + IEnumerable GetPresetItems(int preset_id); + + bool ActivatePreset(int preset_id, int owner_id); + bool ClearPresetTuning(int owner_id); + bool ClearInactiveTuning(int[] active_ids); + + int CreatePreset(DbTuningPreset preset); + int CreatePresetItem(DbTuningPresetItem item); + bool DeletePreset(int preset_id); } } diff --git a/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs b/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs index b7b7fc436..15b54830f 100644 --- a/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs +++ b/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs @@ -22,5 +22,54 @@ public IEnumerable AllCategory(string type, int table) { return Context.Connection.Query("SELECT * FROM fso_tuning WHERE tuning_type = @type AND tuning_table = @table", new { type = type, table = table }); } + + public IEnumerable GetAllPresets() + { + return Context.Connection.Query("SELECT * FROM fso_tuning_presets"); + } + + public IEnumerable GetPresetItems(int preset_id) + { + return Context.Connection.Query("SELECT * FROM fso_tuning_preset_items WHERE preset_id = @preset_id", new { preset_id }); + } + + public bool ActivatePreset(int preset_id, int owner_id) + { + return Context.Connection.Execute("INSERT IGNORE INTO fso_tuning (tuning_type, tuning_table, tuning_index, value, owner_type, owner_id) " + + "SELECT p.tuning_type, p.tuning_table, p.tuning_index, p.value, 'EVENT' as owner_type, @owner_id as owner_id " + + "FROM fso_tuning_preset_items as p " + + "WHERE p.preset_id = @preset_id", new { preset_id, owner_id }) > 0; + } + + public bool ClearPresetTuning(int owner_id) + { + return Context.Connection.Execute("DELETE FROM fso_tuning WHERE owner_type = 'EVENT' AND owner_id = owner_id", new { owner_id } ) > 0; + } + + public bool ClearInactiveTuning(int[] active_ids) + { + return Context.Connection.Execute("DELETE FROM fso_tuning WHERE owner_type = 'EVENT' AND owner_id NOT IN @active_ids", new { active_ids }) > 0; + } + + public int CreatePreset(DbTuningPreset preset) + { + var result = Context.Connection.Query("INSERT INTO fso_tuning_presets (name, description, flags) " + + "VALUES (@name, @description, @flags); SELECT LAST_INSERT_ID();", + preset).FirstOrDefault(); + return result; + } + + public int CreatePresetItem(DbTuningPresetItem item) + { + var result = Context.Connection.Query("INSERT INTO fso_tuning_preset_items (preset_id, tuning_type, tuning_table, tuning_index, value) " + + "VALUES (@preset_id, @tuning_type, @tuning_table, @tuning_index, @value); SELECT LAST_INSERT_ID();", + item).FirstOrDefault(); + return result; + } + + public bool DeletePreset(int preset_id) + { + return Context.Connection.Execute("DELETE FROM fso_tuning_presets WHERE preset_id = @preset_id", new { preset_id }) > 0; + } } } diff --git a/TSOClient/FSO.Server.Database/DatabaseScripts/changes/0027_tuning_presets.sql b/TSOClient/FSO.Server.Database/DatabaseScripts/changes/0027_tuning_presets.sql new file mode 100644 index 000000000..0c350e673 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DatabaseScripts/changes/0027_tuning_presets.sql @@ -0,0 +1,23 @@ +CREATE TABLE `fso_tuning_presets` ( + `preset_id` INT NOT NULL AUTO_INCREMENT, + `name` VARCHAR(128) NULL, + `description` VARCHAR(1000) NULL, + `flags` INT NOT NULL DEFAULT 0, + PRIMARY KEY (`preset_id`)) +COMMENT = 'Presets are collections of tuning info that can be applied and removed at the same time as part of an event.'; + +CREATE TABLE `fso_tuning_preset_items` ( + `item_id` INT NOT NULL AUTO_INCREMENT, + `preset_id` INT NOT NULL, + `tuning_type` VARCHAR(128) NOT NULL, + `tuning_table` INT NOT NULL, + `tuning_index` INT NOT NULL, + `value` FLOAT NOT NULL, + PRIMARY KEY (`item_id`), + INDEX `fso_preset_item_to_preset_fk_idx` (`preset_id` ASC), + CONSTRAINT `fso_preset_item_to_preset_fk` + FOREIGN KEY (`preset_id`) + REFERENCES `fso_tuning_presets` (`preset_id`) + ON DELETE CASCADE + ON UPDATE CASCADE) +COMMENT = 'Individual tuning items that belong to a preset configuration, to be applied as a group. Similar to fso_tuning, built to copy right into it.'; \ No newline at end of file diff --git a/TSOClient/FSO.Server.Database/DatabaseScripts/manifest.json b/TSOClient/FSO.Server.Database/DatabaseScripts/manifest.json index cae6da1fb..dc75844e1 100644 --- a/TSOClient/FSO.Server.Database/DatabaseScripts/manifest.json +++ b/TSOClient/FSO.Server.Database/DatabaseScripts/manifest.json @@ -244,6 +244,14 @@ "requires": [ "3b87e93a-5fae-437b-8ba5-1e46ebb4cd15" ] + }, + { + "id": "2b6e2b3f-81af-4f23-8a69-2428debcfcc5", + "script": "changes/0027_tuning_presets.sql", + "idempotent": false, + "requires": [ + "b21e29a1-8c25-465e-aa43-c7a0d6f44fb8" + ] } ] } \ No newline at end of file diff --git a/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj b/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj index e2ff9fb60..ab1c89113 100644 --- a/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj +++ b/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj @@ -88,6 +88,9 @@ PreserveNewest + + PreserveNewest + @@ -190,6 +193,7 @@ + diff --git a/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs b/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs index 221168491..6bf7c8d92 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs @@ -32,6 +32,7 @@ public enum ElectronPacketType : ushort NhoodCandidateList, BulletinRequest, BulletinResponse, + GlobalTuningUpdate, Unknown = 0xFFFF } diff --git a/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs b/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs index c71f1b0dc..cd4895351 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs @@ -34,7 +34,8 @@ public class ElectronPackets typeof(NhoodResponse), typeof(NhoodCandidateList), typeof(BulletinRequest), - typeof(BulletinResponse) + typeof(BulletinResponse), + typeof(GlobalTuningUpdate) }; static ElectronPackets() diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/GlobalTuningUpdate.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/GlobalTuningUpdate.cs new file mode 100644 index 000000000..02808d8a5 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/GlobalTuningUpdate.cs @@ -0,0 +1,49 @@ +using FSO.Common.Model; +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class GlobalTuningUpdate : AbstractElectronPacket + { + public DynamicTuning Tuning; + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + var dataLen = input.GetInt32(); //TODO: limits? 4MB is probably reasonable. + var data = new byte[dataLen]; + input.Get(data, 0, dataLen); + using (var mem = new MemoryStream(data)) + { + using (var reader = new BinaryReader(mem)) + { + Tuning = new DynamicTuning(reader); + } + } + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.GlobalTuningUpdate; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + using (var mem = new MemoryStream()) + { + using (var writer = new BinaryWriter(mem)) + { + Tuning.SerializeInto(writer); + var result = mem.ToArray(); + output.PutInt32(result.Length); + output.Put(result, 0, result.Length); + } + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj b/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj index c0ad99e40..5e278e108 100644 --- a/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj +++ b/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj @@ -114,6 +114,7 @@ + @@ -153,6 +154,7 @@ + diff --git a/TSOClient/FSO.Server.Protocol/Gluon/GluonPacketType.cs b/TSOClient/FSO.Server.Protocol/Gluon/GluonPacketType.cs index a82e47557..8e69c3c35 100644 --- a/TSOClient/FSO.Server.Protocol/Gluon/GluonPacketType.cs +++ b/TSOClient/FSO.Server.Protocol/Gluon/GluonPacketType.cs @@ -24,6 +24,8 @@ public enum GluonPacketType MatchmakerNotify, CityNotify, + TuningChanged, + Unknown } @@ -59,6 +61,8 @@ public static GluonPacketType FromPacketCode(ushort code) return GluonPacketType.MatchmakerNotify; case 0x0013: return GluonPacketType.CityNotify; + case 0x0014: + return GluonPacketType.TuningChanged; default: return GluonPacketType.Unknown; } @@ -94,6 +98,8 @@ public static ushort GetPacketCode(this GluonPacketType type) return 0x0012; case GluonPacketType.CityNotify: return 0x0013; + case GluonPacketType.TuningChanged: + return 0x0014; } return 0xFFFF; diff --git a/TSOClient/FSO.Server.Protocol/Gluon/GluonPackets.cs b/TSOClient/FSO.Server.Protocol/Gluon/GluonPackets.cs index e86af26ca..7d271d609 100644 --- a/TSOClient/FSO.Server.Protocol/Gluon/GluonPackets.cs +++ b/TSOClient/FSO.Server.Protocol/Gluon/GluonPackets.cs @@ -24,7 +24,8 @@ public class GluonPackets typeof(RequestTaskResponse), typeof(NotifyLotRoommateChange), typeof(MatchmakerNotify), - typeof(CityNotify) + typeof(CityNotify), + typeof(TuningChanged) }; static GluonPackets() diff --git a/TSOClient/FSO.Server.Protocol/Gluon/Packets/TuningChanged.cs b/TSOClient/FSO.Server.Protocol/Gluon/Packets/TuningChanged.cs new file mode 100644 index 000000000..634fcbfe8 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Gluon/Packets/TuningChanged.cs @@ -0,0 +1,32 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSO.Server.Protocol.Gluon.Packets +{ + public class TuningChanged : AbstractGluonCallPacket + { + public bool UpdateInstantly; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + base.Deserialize(input, context); + UpdateInstantly = input.GetBool(); + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + base.Serialize(output, context); + output.PutBool(UpdateInstantly); + } + + public override GluonPacketType GetPacketType() + { + return GluonPacketType.TuningChanged; + } + } +} diff --git a/TSOClient/FSO.Server/FSO.Server.csproj b/TSOClient/FSO.Server/FSO.Server.csproj index 5cc694574..7ee886e5f 100644 --- a/TSOClient/FSO.Server/FSO.Server.csproj +++ b/TSOClient/FSO.Server/FSO.Server.csproj @@ -193,6 +193,7 @@ + diff --git a/TSOClient/FSO.Server/Servers/City/CityServer.cs b/TSOClient/FSO.Server/Servers/City/CityServer.cs index 76e489501..d9bf96ec8 100644 --- a/TSOClient/FSO.Server/Servers/City/CityServer.cs +++ b/TSOClient/FSO.Server/Servers/City/CityServer.cs @@ -72,6 +72,7 @@ protected override void Bootstrap() Kernel.Bind().To().InSingletonScope(); Kernel.Bind().To().InSingletonScope(); Kernel.Bind().ToSelf().InSingletonScope(); + Kernel.Bind().ToSelf().InSingletonScope(); Liveness = Kernel.Get(); diff --git a/TSOClient/FSO.Server/Servers/City/Domain/EventSystem.cs b/TSOClient/FSO.Server/Servers/City/Domain/EventSystem.cs index 417f92a5e..f030ba2a5 100644 --- a/TSOClient/FSO.Server/Servers/City/Domain/EventSystem.cs +++ b/TSOClient/FSO.Server/Servers/City/Domain/EventSystem.cs @@ -4,6 +4,7 @@ using FSO.Server.Framework.Voltron; using FSO.Server.Servers.City.Handlers; using Ninject; +using NLog; using System; using System.Collections.Generic; using System.Linq; @@ -19,16 +20,19 @@ public class EventSystem private CityServerContext Context; private ISessions Sessions; private IKernel Kernel; + private Tuning TuningDomain; + private static Logger LOG = LogManager.GetCurrentClassLogger(); public List ActiveEvents = new List(); public DateTime Next = new DateTime(0); - public EventSystem(IDAFactory da, CityServerContext ctx, ISessions sessions, IKernel kernel) + public EventSystem(IDAFactory da, CityServerContext ctx, ISessions sessions, IKernel kernel, Tuning tuning) { DA = da; Kernel = kernel; Context = ctx; Sessions = sessions; + TuningDomain = tuning; } public void Init() @@ -57,7 +61,7 @@ public void TickEvents() ActiveEvents.AddRange(active); } //TODO: deactivation event - ActivateEvents(newEvts); + ActivateEvents(da, newEvts); } } } @@ -71,9 +75,44 @@ private DateTime Trim(DateTime date, long roundTicks) return new DateTime(date.Ticks - date.Ticks % roundTicks); } - - public void ActivateEvents(List evts) + public void ActivateEvents(IDA da, List evts) { + //ensure events that have not started + + int[] activeIDs; + lock (ActiveEvents) { + activeIDs = ActiveEvents.Select(x => x.event_id).ToArray(); + } + bool changed = false; + try + { + changed = da.Tuning.ClearInactiveTuning(activeIDs); + } + catch (Exception e) + { + LOG.Error(e, "Failed to clear inactive tuning!"); + } + + foreach (var evt in evts) + { + switch (evt.type) + { + case DbEventType.obj_tuning: + try + { + da.Tuning.ActivatePreset(evt.value, evt.event_id); + } + catch (Exception e) + { + LOG.Error(e, $"Failed to activate preset {evt.value}!"); + } + changed = true; + break; + } + } + + if (changed) TuningDomain.BroadcastTuningUpdate(true); + var all = Sessions.Clone(); foreach (var session in all.OfType()) { diff --git a/TSOClient/FSO.Server/Servers/City/Domain/LotServerPicker.cs b/TSOClient/FSO.Server/Servers/City/Domain/LotServerPicker.cs index 9a13ad6a0..256349c0f 100644 --- a/TSOClient/FSO.Server/Servers/City/Domain/LotServerPicker.cs +++ b/TSOClient/FSO.Server/Servers/City/Domain/LotServerPicker.cs @@ -49,6 +49,19 @@ public IGluonSession GetLotServerSession(string callSign) } } + public void BroadcastMessage(object message) + { + List sessions; + lock (Servers) + { + sessions = Servers.Select(x => x.Session).ToList(); + } + foreach (var session in sessions) + { + session?.Write(message); + } + } + public async Task ShutdownAllLotServers(ShutdownType type) { if (AllServersShutdown != null) return false; diff --git a/TSOClient/FSO.Server/Servers/City/Domain/Tuning.cs b/TSOClient/FSO.Server/Servers/City/Domain/Tuning.cs new file mode 100644 index 000000000..4838804e4 --- /dev/null +++ b/TSOClient/FSO.Server/Servers/City/Domain/Tuning.cs @@ -0,0 +1,50 @@ +using FSO.Common.Model; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Tuning; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Gluon.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSO.Server.Servers.City.Domain +{ + public class Tuning + { + private LotServerPicker LotServers; + private DynamicTuning TuningCache; + private IDAFactory DAFactory; + + public Tuning(LotServerPicker LotServers, IDAFactory da) + { + this.LotServers = LotServers; + DAFactory = da; + } + + public void UpdateTuningCache() + { + lock (this) + { + using (var da = DAFactory.Get()) + { + TuningCache = new DynamicTuning(da.Tuning.All()); + } + } + } + + public void UserJoined(IVoltronSession session) + { + if (TuningCache == null) UpdateTuningCache(); + session.Write(new GlobalTuningUpdate() { Tuning = TuningCache }); + } + + public void BroadcastTuningUpdate(bool updateImmediately) + { + LotServers.BroadcastMessage(new TuningChanged() { UpdateInstantly = updateImmediately }); + UpdateTuningCache(); + } + } +} diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs index 69ca1f9ee..e23275f69 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs @@ -26,9 +26,10 @@ public class VoltronConnectionLifecycleHandler : IAriesSessionInterceptor private CityLivenessEngine Liveness; private EventSystem Events; private Neighborhoods Neigh; + private Tuning TuningDomain; public VoltronConnectionLifecycleHandler(ISessions sessions, IDataService dataService, IDAFactory da, CityServerContext context, LotServerPicker lotServers, CityLivenessEngine engine, - EventSystem events, Neighborhoods neigh) + EventSystem events, Neighborhoods neigh, Tuning tuning) { this.VoltronSessions = sessions.GetOrCreateGroup(Groups.VOLTRON); this.Sessions = sessions; @@ -39,6 +40,7 @@ public VoltronConnectionLifecycleHandler(ISessions sessions, IDataService dataSe this.Liveness = engine; this.Events = events; this.Neigh = neigh; + this.TuningDomain = tuning; } public void Handle(IVoltronSession session, ClientByePDU packet) @@ -131,6 +133,7 @@ public async void SessionUpgraded(IAriesSession oldSession, IAriesSession newSes VoltronSessions.Enroll(newSession); Events.UserJoined(voltronSession); Neigh.UserJoined(voltronSession); + TuningDomain.UserJoined(voltronSession); //TODO: Somehow alert people this sim is online? } diff --git a/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs b/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs index e7553ce08..07d5587f8 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs @@ -797,6 +797,19 @@ public void ResetVM() } } + public void UpdateTuning(IEnumerable tuning) + { + Tuning = new DynamicTuning(tuning); + if (Lot == null) return; + if (Lot.Tuning == null || (Lot.Tuning.GetTuning("forcedTuning", 0, 0) ?? 0f) == 0f) + { + Lot.ForwardCommand(new VMNetTuningCmd() + { + Tuning = Tuning + }); + } + } + private static uint PAYPHONE_GUID = 0x313D2F9A; private static uint NHOOD_PAYPHONE_GUID = 0x303CD603; private static uint NHOOD_BULLETIN_GUID = 0x4B489F30; diff --git a/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs b/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs index b6412b5d2..4bb74c8d2 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs @@ -1,4 +1,5 @@ using FSO.Common.DataService; +using FSO.Common.Model; using FSO.Common.Serialization.Primitives; using FSO.Common.Utils; using FSO.Server.Common; @@ -207,6 +208,23 @@ public bool NotifyRoommateChange(int lot_id, uint avatar_id, uint replace_id, Ch return true; } + public void UpdateTuning(bool immediately) + { + LOG.Info("Updating tuning on lot server " + Config.Call_Sign + "..."); + using (var da = DAFactory.Get()) { + var tuning = da.Tuning.All().ToList(); + List lots; + lock (Lots) lots = Lots.Values.ToList(); + foreach (var lot in lots) + { + var container = lot.Container; + lot.InBackground(() => + { + container?.UpdateTuning(tuning); + }); + } + } + } private LotHostEntry GetLot(IVoltronSession session) { diff --git a/TSOClient/FSO.Server/Servers/Lot/Handlers/LotNegotiationHandler.cs b/TSOClient/FSO.Server/Servers/Lot/Handlers/LotNegotiationHandler.cs index cbd876ac6..a68b10f77 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Handlers/LotNegotiationHandler.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Handlers/LotNegotiationHandler.cs @@ -79,5 +79,10 @@ public void Handle(IGluonSession session, NotifyLotRoommateChange request) { Lots.NotifyRoommateChange(request.LotId, request.AvatarId, request.ReplaceId, request.Change); } + + public void Handle(IGluonSession session, TuningChanged request) + { + Lots.UpdateTuning(request.UpdateInstantly); + } } } diff --git a/TSOClient/FSO.Server/Servers/Tasks/Domain/JobBalanceTask.cs b/TSOClient/FSO.Server/Servers/Tasks/Domain/JobBalanceTask.cs index 9cf448f81..d2eabc24e 100644 --- a/TSOClient/FSO.Server/Servers/Tasks/Domain/JobBalanceTask.cs +++ b/TSOClient/FSO.Server/Servers/Tasks/Domain/JobBalanceTask.cs @@ -7,6 +7,8 @@ using FSO.Server.Database.DA; using FSO.Server.Database.DA.DynPayouts; using FSO.Server.Database.DA.Tuning; +using FSO.Server.Domain; +using FSO.Server.Protocol.Gluon.Packets; namespace FSO.Server.Servers.Tasks.Domain { @@ -15,6 +17,7 @@ public class JobBalanceTask : ITask private IDAFactory DAFactory; private bool Running; private TaskTuning Tuning; + private IGluonHostPool HostPool; private static Dictionary TransactionToType = new Dictionary() { @@ -54,10 +57,11 @@ public class JobBalanceTask : ITask 5 //telemarketing }; - public JobBalanceTask(IDAFactory DAFactory, TaskTuning tuning) + public JobBalanceTask(IDAFactory DAFactory, TaskTuning tuning, IGluonHostPool hosts) { this.DAFactory = DAFactory; this.Tuning = tuning; + this.HostPool = hosts; } public void Abort() @@ -161,6 +165,12 @@ public void Run(TaskContext context) } db.DynPayouts.ReplaceDynTuning(dbTuning); + //tell lots tuning has changed + var lots = HostPool.GetByRole(Database.DA.Hosts.DbHostRole.lot); + foreach (var lot in lots) + { + lot.Write(new TuningChanged() { UpdateInstantly = true }); + } } } diff --git a/TSOClient/FSO.Server/Utils/GluonHostPool.cs b/TSOClient/FSO.Server/Utils/GluonHostPool.cs index 17b5bdefb..54fdfa3e3 100644 --- a/TSOClient/FSO.Server/Utils/GluonHostPool.cs +++ b/TSOClient/FSO.Server/Utils/GluonHostPool.cs @@ -118,17 +118,20 @@ public IGluonHost Get(string callSign) public IEnumerable GetByRole(DbHostRole role) { - return Pool.Values.Where(x => x.Role == role); + lock (Pool) + return Pool.Values.Where(x => x.Role == role).ToList(); } public IEnumerable GetAll() { - return Pool.Values; + lock (Pool) + return Pool.Values.ToList(); } public IGluonHost GetByShardId(int shard_id) { - return Pool.Values.FirstOrDefault(x => x.ShardId == shard_id); + lock (Pool) + return Pool.Values.FirstOrDefault(x => x.ShardId == shard_id); } } diff --git a/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs b/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs index 425d1b1c3..a5ecf614a 100644 --- a/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs +++ b/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs @@ -6,6 +6,7 @@ using FSO.Common.DatabaseService.Model; using FSO.Common.DataService; using FSO.Common.Domain.Shards; +using FSO.Common.Model; using FSO.Common.Utils; using FSO.Server.Clients; using FSO.Server.Clients.Framework; @@ -402,13 +403,15 @@ public void ResetGame() public void MessageReceived(AriesClient client, object message) { - if (message is RequestClientSession || - message is HostOnlinePDU || message is ServerByePDU){ + if (message is RequestClientSession || + message is HostOnlinePDU || message is ServerByePDU) + { this.AsyncProcessMessage(message); } else if (message is AnnouncementMsgPDU) { - GameThread.InUpdate(() => { + GameThread.InUpdate(() => + { var msg = (AnnouncementMsgPDU)message; UIAlert alert = null; alert = UIScreen.GlobalShowAlert(new UIAlertOptions() @@ -421,7 +424,12 @@ public void MessageReceived(AriesClient client, object message) Alignment = TextAlignment.Left }, true); }); - } else if (message is ChangeRoommateResponse) + } + else if (message is GlobalTuningUpdate) + { + DynamicTuning.Global = (message as GlobalTuningUpdate).Tuning; + } + else if (message is ChangeRoommateResponse) { } diff --git a/TSOClient/tso.client/Rendering/City/CityContent.cs b/TSOClient/tso.client/Rendering/City/CityContent.cs index 8dca2bb70..8eedda045 100644 --- a/TSOClient/tso.client/Rendering/City/CityContent.cs +++ b/TSOClient/tso.client/Rendering/City/CityContent.cs @@ -1,5 +1,6 @@ using FSO.Client.UI.Framework; using FSO.Common; +using FSO.Common.Model; using FSO.Common.Utils; using FSO.Files; using Microsoft.Xna.Framework; @@ -83,10 +84,11 @@ public void LoadContent(GraphicsDevice gd, int cityNumber) MapData = new CityMapData(); MapData.Load(CityStr, LoadTex, ext); - - //DECEMBER TEMP: snow replace - //TODO: tie to tuning, or serverside weather system. - //ForceSnow(); + + //special tuning from server + var terrainTuning = DynamicTuning.Global?.GetTable("city", 0); + float forceSnow; + if (terrainTuning != null && terrainTuning.TryGetValue(0, out forceSnow)) ForceSnow(forceSnow > 0); //grass, sand, rock, snow, water TerrainTextures[0] = RTToMip(LoadTex(gamepath + "gamedata/terrain/newformat/gr.tga"), gd); @@ -167,7 +169,7 @@ public void LoadContent(GraphicsDevice gd, int cityNumber) for (int x = 0; x < 16; x++) RoadCorners[x].Dispose(); } - public void ForceSnow() + public void ForceSnow(bool toGrass) { var dat = new Color[VertexColor.Width * VertexColor.Height]; VertexColor.GetData(dat); @@ -178,16 +180,30 @@ public void ForceSnow() { var old = dat[i]; var greater = Math.Max(old.R, old.G); - if (old.B < greater) + if (!toGrass) { - //make this pixel grayscale - dat[i] = new Color(greater, greater, greater); + if (old.B < greater) + { + //make this pixel grayscale + dat[i] = new Color(greater, greater, greater); + } } var oldType = typeC[i]; - if (oldType == new Color(0, 255, 0) || oldType == Color.Yellow) + if (toGrass) //change snow to grass + { + if (oldType == Color.White) + { + typeC[i] = new Color(0, 255, 0); + type[i] = 0; + } + } + else { - typeC[i] = Color.White; - type[i] = 3; + if (oldType == new Color(0, 255, 0) || oldType == Color.Yellow) + { + typeC[i] = Color.White; + type[i] = 3; + } } } diff --git a/TSOClient/tso.client/UI/Panels/UILotPage.cs b/TSOClient/tso.client/UI/Panels/UILotPage.cs index 3548af04b..4a8354e5c 100644 --- a/TSOClient/tso.client/UI/Panels/UILotPage.cs +++ b/TSOClient/tso.client/UI/Panels/UILotPage.cs @@ -200,7 +200,7 @@ public UILotPage() case LotCategory.community: return HouseCategory_CommunityButtonImage; default: - return null; + return HouseCategory_CommunityButtonImage; } }).WithBinding(HouseCategoryButton, "Position", "Lot_Category", x => { diff --git a/TSOClient/tso.common/Model/DynamicTuning.cs b/TSOClient/tso.common/Model/DynamicTuning.cs index 88c4da7ee..f0bd3f985 100644 --- a/TSOClient/tso.common/Model/DynamicTuning.cs +++ b/TSOClient/tso.common/Model/DynamicTuning.cs @@ -9,6 +9,12 @@ namespace FSO.Common.Model { public class DynamicTuning { + //global tuning: + // city - + // 0: terrain + // 0: forceSnow (0/1/null) + public static DynamicTuning Global; + //string type/iff, int table, int index. public Dictionary>> Tuning = new Dictionary>>(); public const int CURRENT_VERSION = 0; diff --git a/TSOClient/tso.content/Content/Patch/TSOPorts/clothingboothvac.piff b/TSOClient/tso.content/Content/Patch/TSOPorts/clothingboothvac.piff new file mode 100644 index 0000000000000000000000000000000000000000..6ed3e5dac8879f9c718cfaa5f34561f346b4789a GIT binary patch literal 1163 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 literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/TSOPorts/petawards.piff b/TSOClient/tso.content/Content/Patch/TSOPorts/petawards.piff new file mode 100644 index 0000000000000000000000000000000000000000..2db21a58a326120d4cbb1d385f199e02e7bd2695 GIT binary patch literal 3263 zcmd5dkUV6mN-I<%2Sa>xCPYvM5!no9`5>HSvPDQFy;n6ITF7 zy_M;7v6oIwO}^l5S{%7u9bF;Fh5#p@-^!o|YRz-4ubS491`-*1YIzW?co>I)vo zJ>;26FIFsAdRIH}}+#n+cTBzVwv| zo}=KTFEgMMtK=ZK3eR6d`MhTTTaL>oi0S!_k48Pw>UZymLAyI0Nf9yV9yntWG0R}# zS>78{!?wVTy45@z?@>%NRwth8Y`GjYE1jX&cf+bEceeZP-IFwyD5ynrfT~M-H1d$0M+b7`VIiB)vH?8# zR6nth*3Ta?GfGHw<}fXX5-jTFT3s(Z29RKtL?Y0D@m@%bcJeE?f$w*+>AB5gtR8qx zS;Sbetnqgl7$X7*-)IiD4#4rV+>d#S=L-fhBoH)g%vNTROlg z$_c#yBBqe`e{SLgG@)JEgdB>gy>ZBtb5pj!t?0(ndZ&TG(C=(-d5vyoYcOgwyukoA zDh@UZ;}de*^M~xGPtd9?8pa-=GN>lnU zK$p4Dp5yliL-kp}VfT1*Z-3Vcy9Jr6Z)|SF=?aB7zl3w@5*A}IjA4$kbjAXdu);~t zl6n@WyJ12P-&$#i;mwtkNnPS_@x@VRH1L`)iED1VF>1R*Ph5F@O*Fl2`X{2*_In(b TIQ_FB4-aD|y1}0~wo~(Ooh~J; literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/TSOPorts/petawards.spf.piff b/TSOClient/tso.content/Content/Patch/TSOPorts/petawards.spf.piff new file mode 100644 index 0000000000000000000000000000000000000000..b8e83948d27ed54805b04b3242f2c7bf3aa8e89c GIT binary patch literal 54060 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 N3mM4fNjqQje*mdWPc@nusjv< zG~mgB$g(}`q!drdSkwlGM$V+bzMQ0P59hkXiVPwDjU%#ddqw}2O#f{D0uWKGRkEad z;p2)p{@8-)*0nHn&K|}hC$t*lOj~NTT6d>wJbf$+&)d)|lK!LlE5I<7<_?^fq#s-d zBRpqkBqjd~SFH&3xQ=jM9zZz6;R!9=-t(O6I~zt?Y_FUpdD5+yuB6v3Y!z1n`E~7lBPp$*KO9-zmx>6-7{KooWrR>DPFnmo;3o1MEVMeg^@X07QdRHrB#908VBihfS($na$E<6LQ)eW zW*%qEWzVHtDqUSa_pnN3oMI)jjB6g7A%&wkTA5qk(&%Y(C2ei2Z>;yV^TkoP1wzcg z{qWQ96c0k|eAMnAacj^jCa=}q8KJ*Z$9U3hBj>i7`qZ;J=kn}bVO?_)jmVE#&|>Kl z7G!dvNV#mz$(eHjFqlv*Evpt&uIMB0WG)ig#=frDO;R*tqP5dAN!7wXQE}Y&cueyJ zznTmCW-upf#e|-0tUtn?V`<%B^nzlCj{uH3y37)_Ta#S4EY!UA_$jjxZr7s7>YBM| z8U7kF63mx-jFTB=f^VI3^Zq4dmix}q#-dvR>Eu`=DH$;o&F~YRV{U zgJ%G*bp+pQwvv#z91q(*yDRvX`;jhZq@=+BBgL0>lxYsLGsG`3%2=_tKP;u&54_x6 zn*9O3;|duK+Wz{z&Q>+`5~)`3JuWk+b<%Ae(x{w#(BWG)7}io|zc){0JoRnsqRTe_ p-MWl)3U!@4Af!Z2SiT;2)YgAC~|C literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.piff b/TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.piff new file mode 100644 index 0000000000000000000000000000000000000000..382028ede98fc55ec560be14dbd38718d61340d1 GIT binary patch literal 2091 zcmcgsPj6dA5TDui{QlT}@z#EB64O*GgHQ}fP}3r$ii<7V!c|&ZjRTaM73^ZEa-zl+ z>LFEo>j5G4!jWq~0bhUvC$8`fIOW8VLt=ijel|{JBraw2o@RD-=Qp!6v(sueO|!Mv zFuOY+-|jx>G)%L-x7YrnQ8%>*X211SgUp@k-B!cY+wJ@JYVCdVzPVfdvbAq+5UBQ9 z^>eUOy+PC(jr)&#hvVVV$ZVVL(Zh$2`liz#o6e)&Q_~&{K5CD|=HJM9p6oc_idea_ zR_rUfICqJOQZpHP$aCk{|C@8uZR~dia|ylST=C5LE}ZYcw}R13#*#X%W|PSKQ{?0R z_!xH_96cG0d&AMra4;~(z475UOB^jzF%_0TMAYq8YcF7+=>@%lBQ3_K>`)r_*PfPj z2bUL;cyHc(dT$qe0utTZpi?GhQUMc6E(D2DEte%AHzG%Ib^4F6}4-32Cu79z1P+65QPMDO(8j-D(ZIbcO*(p zTEB$;3VxS4g%sxsTsjy%Iq83GHoxtim~vCvtAJE94^@Uut6r{z_2J|GVHO%#aE?c7 zmrQaF1t}htE2RT3!wXm$ri{boO5X7)kK(X>$!sHG{S%@H@k8$ju6*nwWYm^ceC7;P z&)(zE!Q0H9e?WM#R*oRU(8>yOb~+(S7Pq9OBXUb|(@aHd{KD~sBHK_37j@`IIL^b_ zhaLz1HtC5EjA=C$rHgzn1Af!b zcf?0wmXyLELNLLah#93K$w!E|z%mL6!e$zkmL;RbLt2h1&H_bv zJ`0X54;M~Lw@BH65rGxqnfVGN*OeRlp3fA#BLN49C~#7|P1h!Y4`ZP%UvXS;wx=ts zskol8J0f2#Vq#d@qSdRmv{-G?&MjVzlAWKqFgpZ)rI@IXHJ?vx1GD1Jl%rr3VvMrpT6RxuTs9l6W4W=R218iU(GxTkzu-U zW6sG2uap!y7l5$AiR--WuDm_~@M72E93Z4+kvj{Aa>P)HQNbD&^k2ZS*O-Uo!Jr~X zVtXXk>%jFcMJ-1-botlc&Nf}w;>{kh;Gydmt|)cIc{C4I3V22cAex&7b4fhPfS2zN z*e`-eO3(;E$OA!|;uUZb?{UO3?}dsQV(f5Uvx~8u5av&PHh+GHE)YHQ_kclOR>6Sy R9P9@y5*xuzM9a!de*?Z?*YE%U literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.spf.piff b/TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.spf.piff new file mode 100644 index 0000000000000000000000000000000000000000..65e033633059c0258cd3139b0eb420f0bddad21a GIT binary patch literal 16017 zcmeI2|4$ob9LJx}wOuVCj07+yOmhj-DayzeOq^2)Wv%I?6vh@{SvJYOWNNoHp_^b8 zgl#wiERzig&PK++^ZPKA+F?^tpzDK`|I=4T!sUzWMgSp7wwkjI_2!x&r$|e~;K1IvgP4{^oEfAnuDq z4z&3ro#HEExVbyjDefZpX%7X1gfw;l+Od=IShRQG)VcF#~ zGvFrR0xC-`!7*|WVHUu4cv-l~(k%2jWOE>^W`~1Vm7-XUy}2au-pejRj(~M#UY3r) zCQM};mLEH(@@DLZ9mIw^f7T&8i1RYuESfBSAP^GVgq6K8aa=T*ZCE93GLUl++XRGl z&2SJ~r)|ORd>^OO^oxGcVAglDV2Z2FuutdVAhwA9h9DNT#-0Cj#I@Gl!UN9S^# zL%S`zv-;5Y_#qcJz^gN=yU3mtUo(gk|cTSA~5Cg|0#uG33nlge^&wA@Fa z4(Q0xXLK|Xqi2qW|8sJrZYy&&BWzsH5vXE2W$CAk<#~%LFzPD+{abj6z!t@PB%r^Q z@EyJP{Vh5(cT;t9{A%Sk<%~Ie;fH5T)1U9%p}#*^N^5DQ6#xU~047eh(aFgPx-g%l z*QV!az(b${&`2Z_s#A<%1-eti4yZs!MreO;Xbr=f7qyrvX}Sv1x603_W^nFNydM77 z2$TT*^VeniO)^OrN|2)fNy;F)m``p^mn*B%{hgY+PM`d87dieLWqB+nm=@W*1FpmT zs%HugoM=<1aVBibz;Sqy=oOV$J<9854lTG9p-~4c$!uMQ>-5K$DZSb%tTh>!D&;Oz z&y(WlQG`TktbJRIIrs-lD*+a%&`iL$%E#A2$UBfKHR~zB;+w@eU!JWW@2(d-`eB(Z zI()CL6t9V~Q3V}q6vGyYR;3;kiK4vt)rDuG!x0Qp`~yL1y9cO${{dxT*Kmi}HXWdr zy+=XBpmJtdj?KB8)m1*Aw->%un(Fft*ujyp(9B%YeS0+(s~#m!Q3?;cP?kLP7PQ*T zuy%OxQ#Fv7{;chXKKN)|pLj|#Zd!b__7(1(Koij9*R!S_lBFj7EXgWl)RWC{VO-BtCNn~?1~gZaVJ9W*&} zjsCE_{RDw7P@*mq5Btv2iP!RY^f*0#xG4)nU4`kB)L>Nxi0>06n-)d=p#Nsxad?Q%ZQFuF`K9?A)a!wVi>xD3fmu=cR3S{2 zKjwoE`gS+q{@|F!*tossu|<9`itN=Ji^KR(zN<5OQlxSq^vY5f|D_roMbKn zjyi?RL98QAaU60GZ!E_Z?3|vuA#>ta|GQO#pj__z?BtVv`wMk$F8)|`ZJ~TO{+y?5 z{?yK0d0~dF#od)7;ugJG4x_)YTKWk2YirtQI$wEWJYEY|Tk_>wKJVk`WGqx2ogAH< PyfYR9j!uqFtX}>Du*DfR literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.str.piff b/TSOClient/tso.content/Content/Patch/TSOPorts/petplayfountain.str.piff new file mode 100644 index 0000000000000000000000000000000000000000..58edee15a20baef12606e034d369fe7970c48832 GIT binary patch literal 1386 zcmb7D&2G~`5S~yFCt6Bdfy8AH5&{V)A`%xaD5bFoYDrXED&;n@*Rk8pj%BYKxaCfW}w&06-=ilg-$a&U1D9@pF3};Cf&= z2iS_+E_r`ft;36|OZcK{4Ri}w$>#Th&yA+s zD~?-HN@k>xB6l||W#MMD>iOhe*8n!UhK5?yCMnpfsn16cDsZ}EHgfDDaP z>!Qg(Su0noCI0XEDhvYoV0UhQ2Z+d$=2bKQW}FLPGfWj8kyTDR_?q2>pBfGEJLO4dxyU#tkOZf4W9%(k2S&sB_#+hl7xG97XX}*rQ-biMa_ur+(jr)QjVb^B_)W zm-_y-N$3!);KaY=@~ozSPGT}enLUigWqw!CKJ~K4GA~Ar>u1GN{o-#~v-i6;R|&Cb zY2=P^!0RAaPjvK@^kVJ zR!~#$b&T)~Rxo4$g5Z!KWgxYRQJ;aqIWZ?kA*3iZwHPErI{5Qngo%Mc!Z}1iSHUG; z!6mi0Br`9uBr`ux!8bLplz~AMB84GNlvM$qZf-z@6B&U-3c?7z%(OIxg2a;K45Cbd qC}L#bM>B$vAtc1HxB)0Fz|g?39!N8TFe4*3BO^nRFe8HihIatgxj;Dp literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/lampceiling2.piff b/TSOClient/tso.content/Content/Patch/lampceiling2.piff new file mode 100644 index 0000000000000000000000000000000000000000..995fc97a8281aaf0c4b90b53558167032ad4ca7e GIT binary patch literal 336 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 literal 0 HcmV?d00001 diff --git a/TSOClient/tso.content/Content/Patch/personglobals.str.piff b/TSOClient/tso.content/Content/Patch/personglobals.str.piff new file mode 100644 index 0000000000000000000000000000000000000000..f47dfa0745c3eac30d3c07d23388e24ea1158acb GIT binary patch literal 218 zcmeZtb5n5h^l?=%(lfORi41U6aP#-^@eg-(QE-Y>2=P^!0RAaPjvK@^kVJ zR!~#$b&T)~Rxo4$f&fo9Hy|~O5l9rI78U2`rRU@)CFT_CWu~Pm6eN};XMmIu1sE9w zF^phj2o4ES)(09dz@X3A1~i8mgc%uuh`~vUkwM%!L_t@J|i0wBQp~KnGOpw delta 16 YcmbQse1~a*JEP1*j|fKQi3@iF04-Aml>h($ diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs index 043ff9a60..84e0ab265 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs @@ -7,6 +7,7 @@ using FSO.SimAntics.Engine.TSOTransaction; using FSO.SimAntics.Model.TSOPlatform; using FSO.SimAntics.NetPlay.Drivers; +using FSO.SimAntics.Test; using System; using System.Collections.Generic; using System.IO; @@ -183,7 +184,18 @@ public override bool Execute(VM vm, VMAvatar avatar) } vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Fixed " + fixCount + " objects.")); break; - + case "testcollision": + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Debug, $"Scanning collision for lot { vm.TSOState.Name }.")); + try + { + var collisionValidator = new CollisionTestUtils(); + collisionValidator.VerifyAllCollision(vm); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Debug, "No issue detected with collision.")); + } catch (Exception e) + { + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Debug, e.Message)); + } + break; } return true; } diff --git a/TSOClient/tso.simantics/Test/CollisionTestUtils.cs b/TSOClient/tso.simantics/Test/CollisionTestUtils.cs index 7a4516809..9e7a6f549 100644 --- a/TSOClient/tso.simantics/Test/CollisionTestUtils.cs +++ b/TSOClient/tso.simantics/Test/CollisionTestUtils.cs @@ -9,6 +9,12 @@ namespace FSO.SimAntics.Test { public class CollisionTestUtils { + public string EntityInfo(VMEntity ent) + { + return $"{ent.ToString()} at {ent.Position.ToString()}. Contained in { ent.Container?.ToString() ?? "null" }, " + + $"Dead: { ent.Dead.ToString() }, Flags: { ent.GetValue(Model.VMStackObjectVariable.Flags).ToString("X4") }."; + } + public void VerifyAllCollision(VM vm) { // verifies that static and dynamic obstacles are in a valid state @@ -31,17 +37,48 @@ public void VerifyAllCollision(VM vm) { if (rect.x1 != footprint.x1 || rect.x2 != footprint.x2 || rect.y1 != footprint.y1 || rect.y2 != footprint.y2) { - throw new Exception("Static footprint mismatch with .Footprint on object!"); + throw new Exception($"Static footprint mismatch with .Footprint on object! \n{EntityInfo(ent)}"); } } - if (rect != ent.Footprint) throw new Exception("Out of date footprint in static!"); - if (!ent.StaticFootprint) throw new Exception("Object with dynamic footprint still present in static!"); + if (rect != ent.Footprint) throw new Exception($"Out of date footprint in static! \n{EntityInfo(ent)}"); + if (!ent.StaticFootprint) throw new Exception($"Object with dynamic footprint still present in static! \n{EntityInfo(ent)}"); + if (ent.Dead) throw new Exception($"Dead entity in static! \n{EntityInfo(ent)}"); + } + } + foreach (var ent in ents) + { + if (ent.StaticFootprint && ent.Footprint != null) throw new Exception($"Object with static footprint missing from static list. \n{EntityInfo(ent)}"); + } + + ents = new HashSet(room.Entities); + var dyn = room.DynamicObstacles; + foreach (var entry in dyn) + { + var rect = entry as VMEntityObstacle; + if (rect != null) + { + var ent = rect.Parent; + ents.Remove(rect.Parent); + + var footprint = ent.Footprint; + if (footprint != null) + { + if (rect.x1 != footprint.x1 || rect.x2 != footprint.x2 || rect.y1 != footprint.y1 || rect.y2 != footprint.y2) + { + throw new Exception($"Dynamic footprint mismatch with .Footprint on object! \n{EntityInfo(ent)}"); + } + } + + if (rect != ent.Footprint) throw new Exception($"Out of date footprint in dynamic! \n{EntityInfo(ent)}"); + if (ent.StaticFootprint) throw new Exception($"Object with static footprint still present in dynamic! \n{EntityInfo(ent)}"); + if (ent.Dead) throw new Exception($"Dead entity in dynamic! \n{EntityInfo(ent)}"); } } + foreach (var ent in ents) { - if (ent.StaticFootprint && ent.Footprint != null) throw new Exception("Object with static footprint missing from static list."); + if (!ent.StaticFootprint && ent.Footprint != null) throw new Exception($"Object with dynamic footprint missing from dynamic list. \n{EntityInfo(ent)}"); } } } diff --git a/TSOClient/tso.simantics/VMContext.cs b/TSOClient/tso.simantics/VMContext.cs index 5fe4cc5ca..d17f3acc5 100644 --- a/TSOClient/tso.simantics/VMContext.cs +++ b/TSOClient/tso.simantics/VMContext.cs @@ -903,7 +903,7 @@ public void RegisterObjectPos(VMEntity obj, bool roomChange) AddWindowPortal(obj, room); } obj.SetRoom(room); - if (obj.GetValue(VMStackObjectVariable.LightingContribution) > 0 || !obj.MovesOften) + if ((obj.GetValue(VMStackObjectVariable.LightingContribution) > 0 || !obj.MovesOften) && obj.GetValue(VMStackObjectVariable.Hidden) == 0) DeferredLightingRefresh.Add(room); else if (obj.GetValue(VMStackObjectVariable.RoomImpact) > 0) RefreshRoomScore(room); diff --git a/TSOClient/tso.simantics/engine/VMMemory.cs b/TSOClient/tso.simantics/engine/VMMemory.cs index ab115bac4..fb84354ee 100644 --- a/TSOClient/tso.simantics/engine/VMMemory.cs +++ b/TSOClient/tso.simantics/engine/VMMemory.cs @@ -578,7 +578,7 @@ public static short GetEntityDefinitionVar(OBJD objd, VMOBJDVariable var, VMStac case VMOBJDVariable.TileWidth: return (short)objd.TileWidth; case VMOBJDVariable.LotCategories: - return 0; //NOT IN OBJD RIGHT NOW! + return (short)objd.LotCategories; case VMOBJDVariable.BuildModeType: return (short)objd.BuildModeType; case VMOBJDVariable.OriginalGUID1: diff --git a/TSOClient/tso.simantics/entities/VMEntity.cs b/TSOClient/tso.simantics/entities/VMEntity.cs index 7052ebab8..4a7e1ffa2 100644 --- a/TSOClient/tso.simantics/entities/VMEntity.cs +++ b/TSOClient/tso.simantics/entities/VMEntity.cs @@ -142,6 +142,7 @@ public virtual bool MovesOften if (Slots == null) return false; if (!Slots.Slots.ContainsKey(3)) return false; var slots = Slots.Slots[3]; + if (slots.Count > 20) return true; return MovedSelf; } } diff --git a/TSOClient/tso.simantics/primitives/VMDropOnto.cs b/TSOClient/tso.simantics/primitives/VMDropOnto.cs index f2a969486..99977742e 100644 --- a/TSOClient/tso.simantics/primitives/VMDropOnto.cs +++ b/TSOClient/tso.simantics/primitives/VMDropOnto.cs @@ -25,6 +25,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe var item = context.Caller.GetSlot(src); if (item != null) { + if (item is VMGameObject) item.WorldUI?.PrepareSlotInterpolation(); var itemTest = context.StackObject.GetSlot(dest); if (itemTest == null) { diff --git a/TSOClient/tso.world/LMap/LMapBatch.cs b/TSOClient/tso.world/LMap/LMapBatch.cs index 912d85f00..07c67f0ff 100644 --- a/TSOClient/tso.world/LMap/LMapBatch.cs +++ b/TSOClient/tso.world/LMap/LMapBatch.cs @@ -402,6 +402,7 @@ public LightData BuildOutdoorsLight(double tod) public void DrawRoom(Room room, RoomLighting lighting, bool clear) { var size = Blueprint.Width - borderSize; + LightEffect.Parameters["floorShadowMap"].SetValue(ObjShadowTarg); LightEffect.Parameters["TargetRoom"].SetValue((float)room.RoomID); var bigBounds = new Rectangle(lighting.Bounds.X * resPerTile, lighting.Bounds.Y * resPerTile, lighting.Bounds.Width * resPerTile, lighting.Bounds.Height * resPerTile); bigBounds = Rectangle.Intersect(bigBounds, new Rectangle(0, 0, size * resPerTile, size * resPerTile)); diff --git a/TSOClient/tso.world/components/EntityComponent.cs b/TSOClient/tso.world/components/EntityComponent.cs index 508f2a10f..ac204eb4b 100644 --- a/TSOClient/tso.world/components/EntityComponent.cs +++ b/TSOClient/tso.world/components/EntityComponent.cs @@ -73,7 +73,19 @@ public override Vector3 Position return Vector3.Lerp(_Position, SnapSelfPrevious, _IdleFramesPct); } } - else return Container.GetSLOTPosition(ContainerSlot, false); + else + { + if (_IdleFramesPct <= 0 || PreviousSlotOffset == null) + { + return Container.GetSLOTPosition(ContainerSlot, false); + } else + { + var oldP = Container.Position + PreviousSlotOffset.Value; + var newP = Container.GetSLOTPosition(ContainerSlot, false); + if (Vector3.Distance(oldP, newP) > 1.5) oldP = newP; + return Vector3.Lerp(newP, oldP, _IdleFramesPct); + } + } } set { @@ -110,12 +122,24 @@ public void PrepareSnapInterpolation() { _IdleFramesPct = 1f; SnapSelfPrevious = _Position; + PreviousSlotOffset = null; } } + public Vector3? PreviousSlotOffset; + public void PrepareSlotInterpolation() { - //unimplemented + _IdleFramesPct = 1f; + SnapSelfPrevious = _Position; + if (Container != null) + { + PreviousSlotOffset = Container.GetSLOTPosition(ContainerSlot, false) - Container.Position; + } + else + { + PreviousSlotOffset = null; + } } public Vector3 UnmoddedPosition diff --git a/TSOClient/tso.world/components/TerrainComponent.cs b/TSOClient/tso.world/components/TerrainComponent.cs index ffa84886d..3e3d98e4c 100644 --- a/TSOClient/tso.world/components/TerrainComponent.cs +++ b/TSOClient/tso.world/components/TerrainComponent.cs @@ -18,6 +18,7 @@ using FSO.Common; using FSO.Common.Utils; using FSO.LotView.LMap; +using FSO.Common.Model; namespace FSO.LotView.Components { @@ -77,17 +78,32 @@ public void UpdateTerrain(TerrainType light, TerrainType dark, short[] heights, //TODO: tie to tuning, or serverside weather system. LightType = light; DarkType = dark; - //ForceSnow(); + + //special tuning from server + var forceSnow = DynamicTuning.Global?.GetTuning("city", 0, 0); + if (forceSnow != null) ForceSnow(forceSnow > 0); + GrassState = grass; GroundHeight = heights; UpdateLotType(); TerrainDirty = true; } - public void ForceSnow() + public void ForceSnow(bool toGrass) { - if (LightType == TerrainType.GRASS || LightType == TerrainType.SAND) LightType = TerrainType.SNOW; - if (DarkType == TerrainType.SAND) DarkType = TerrainType.SNOW; + if (toGrass) + { + if (LightType == TerrainType.SNOW) + { + LightType = TerrainType.GRASS; + if (DarkType == TerrainType.SNOW) DarkType = TerrainType.GRASS; + } + } + else + { + if (LightType == TerrainType.GRASS || LightType == TerrainType.SAND) LightType = TerrainType.SNOW; + if (DarkType == TerrainType.SAND) DarkType = TerrainType.SNOW; + } } public void UpdateLotType() diff --git a/TSOClient/tso.world/model/LotTilePos.cs b/TSOClient/tso.world/model/LotTilePos.cs index bd47753d6..ddc68a6e4 100644 --- a/TSOClient/tso.world/model/LotTilePos.cs +++ b/TSOClient/tso.world/model/LotTilePos.cs @@ -129,6 +129,11 @@ public Point ToPoint() return new Point(x, y); } + public override string ToString() + { + return $"({x}, {y}, {Level})"; + } + public static LotTilePos OUT_OF_WORLD = new LotTilePos(-32768, -32768, 1); public void Deserialize(BinaryReader reader)