
Tyrantosaurus
Member-
Posts
7 -
Joined
-
Last visited
Reputation
0 NeutralAbout Tyrantosaurus
- Birthday October 27
Personal Information
-
Location
Canada
-
Tyrantosaurus started following Spirited
-
I've been looking into downgrading this to work on lower patches (~4348). There's some obvious things, like the check for 64 bit would have to be removed/changed to 32. But other than that I'm not actually sure what I'd have to change to make it work. It doesn't want to inject into these lower patches. Any ideas on what would have to be changed? Edit: Wow I was stuck on this for so long, and now like 10 minutes after I post this I figured it out. I _thought_ I had changed this to compile to 32bit, but apparently not. I think that's all it took. At first I was using TH32CS_SNAPMODULE32 for taking the snapshot, but that's only needed if running a 64 bit process. Doh.
-
Yeah that is how I have it. Essentially everything that happens on a map is queued in that map's single channel. Each map has a tick that queues a "Tick" event, which does things like removing status effects after they have expired, or removing items from the map.
-
Little update on this. I've went back to the event system, it scales better and works with events that originate outside of the client. For instance, anything that occurs after a set amount of time, like items despawning. There was no way to make this work without a client attached to the packet processor queue. Interesting, which packets for example? I have every packet (except for MsgRegister, MsgConnect) handled through the map event queue, it ensures that any data being processed is in sync.
-
After doing some more pondering, I think I am going to refactor the packet processor and essentially merge it with the map processor. So the partition IDs will become the character's map ID, or 0 before the MsgConnect packet is sent. This way I can handle packets directly since they are already part of that map's queue. It simplifies the code as I no longer need packet handlers and event handlers, since they would be one and the same. If I have an event that is not generated by the client, I can just queue a packet to that client directly. Custom packets could be created as needed too. What do you think of this approach?
-
Hello again, I've started work on a screen system for the comet server base. After reading through the Wiki page and cross referencing a few available sources, I've gotten a system that works fairly well. The only issue I have now is when a player jumps from off screen into the screen of an observer from off screen, to the observer it appears that the player teleports to their jump location and jumps "in place" instead of the jump animation showing them coming from off screen. (Edit: This may be what the wiki page is referencing when it says "As an actor moves into the observer's screen, the movement is disregarded and the player is spawned into the screen.", but it seems way worse than I remember) Here's a video that shows this in action: https://streamable.com/flqspw I know why this is, as I send the MsgPlayer packet and then the jump packet to the observer. But if I don't do it this way, the player does not render correctly to the observer. I tried sending the MsgPlayer packet with the previous X and Y of the player, but since it was out of the screen bounds it was ignored by the observer's client, which then requests the MsgAction packet to render the player. Any help would be appreciated. Edit: I may have solved this with Math Stolen From The Internet™. I draw a line from the players previous coordinates to their new coordinates, I then find the furthest point away from the observer up to a max of 18 along that line. I use that point to spawn the player at, so when the jump animation happens occurs from off screen. Here's what it looks like now: https://streamable.com/6mu2it It feels hacky, but it looks much better from the client's point of view.
-
Thanks for the quick reply Spirited! Thank you for the compliment, I also thought it was fun. Good call on the multiple channels, I will definitely avoid that. I like the optimization idea of combining multiple maps into a single processor. I wonder if I could experiment with having a single MapProcessor and split it out once it reaches a certain size or something? But I won't worry about that for now. I think I've gotten a pretty decent system down so far. I'm just getting back into C# so there's probably some improvements to be made, but here's what I got: namespace Comet.Game.MapSystem { using Comet.Game.MapSystem.Events; using System.Collections.Concurrent; using System.Threading; internal class WorldProcessor { private readonly ConcurrentDictionary<uint, MapProcessor> _keyValuePairs = new(); public void Queue(uint mapID, params IMapEvent[] events) { MapProcessor mapProcessor; while (!_keyValuePairs.TryGetValue(mapID, out mapProcessor)) if (_keyValuePairs.TryAdd(mapID, mapProcessor = new MapProcessor(mapID))) { mapProcessor.Enqueue(new MetaInitMap()); mapProcessor.StartAsync(new CancellationToken()).ConfigureAwait(false); break; } foreach (var @event in events) mapProcessor.Enqueue(@event); } } } namespace Comet.Game.MapSystem { using Microsoft.Extensions.Hosting; using System; using System.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Comet.Map; using Comet.Game.MapSystem.Events; using System.Collections.Concurrent; using System.Collections.Generic; public class MapProcessor : BackgroundService { private readonly Channel<IMapEvent> _channel = Channel.CreateUnbounded<IMapEvent>(); private readonly ConcurrentDictionary<uint, IMapEntity> _entities = new(); private readonly uint _mapID; private Map? _map; private CancellationToken _stoppingToken; public Map Map => _map.Value; public uint MapID => _mapID; public MapProcessor(uint mapID) { _mapID = mapID; } public void LoadMap() { if (_map.HasValue) return; _map = MapLoader.LoadMap(Program.Config.Meta.ConquerDirectory, _mapID); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _stoppingToken = stoppingToken; while (!stoppingToken.IsCancellationRequested) { var @event = await _channel.Reader.ReadAsync(stoppingToken); await ProcessAsync(@event); } } public void Enqueue(IMapEvent @event) { if (_stoppingToken.IsCancellationRequested) return; _channel.Writer.TryWrite(@event); } private async Task ProcessAsync(IMapEvent @event) { try { await (@event.Type switch { MapEventType.MetaInitMap => MetaInitMap.ProcessAsync(this, @event as MetaInitMap), MapEventType.SpawnEvent => SpawnEvent.ProcessAsync(this, @event as SpawnEvent), MapEventType.JumpEvent => JumpEvent.ProcessAsync(this, @event as JumpEvent), MapEventType.WalkEvent => WalkEvent.ProcessAsync(this, @event as WalkEvent), _ => throw new NotImplementedException(string.Format("Unhandled Map Event Type: {0}", @event.Type)) }); } catch (NotImplementedException ex) { Console.WriteLine(ex.Message); } catch (Exception ex) { Console.WriteLine(ex.Message); if (ex.InnerException != null) Console.WriteLine(ex.InnerException.Message); } } public void AddEntity(IMapEntity entity) { _entities.TryAdd(entity.ID, entity); } public void RemoveEntity(IMapEntity entity) { _entities.TryRemove(entity.ID, out _); } public IEnumerable<IMapEntity> Entities() { return _entities.Values; } } }
-
Hi all, I've started tinkering around with Spirited's Comet source and have started with implementing a "Map System", and I'd love some feedback on my approach. Following reading Spirited's blog post on Multi-Threaded Game Server Design, my thoughts are like this: A WorldProcessor with a ConcurrentDictionary linking a map ID to a MapProcessor. The world processor will handle IMapEvents and pass them to the appropriate MapProcessor based on the event's map ID. This allows for loading maps on the fly, as well as unloading maps as needed. The MapProcessor is a BackgroundService utilizing a task channel as a queue that processes IMapEvents (maybe multiple channels, one for each entity type [player, mob, etc]?). This is similar to the existing PacketProcessor. With this system, instead of packets being handled directly, they will call WorldProcessor with an event, ensuring packets are handled in order. Thanks