Beispiel #1
0
        private static void DoPlayerEnterWorld(Session session, Character character, Biota playerBiota, PossessedBiotas possessedBiotas)
        {
            Player player;

            if (playerBiota.WeenieType == (int)WeenieType.Admin)
            {
                player = new Admin(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else if (playerBiota.WeenieType == (int)WeenieType.Sentinel)
            {
                player = new Sentinel(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else
            {
                player = new Player(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }

            session.SetPlayer(player);

            session.Player.PlayerEnterWorld();

            if (character.TotalLogins <= 1 || PropertyManager.GetBool("alwaysshowwelcome").Item)
            {
                // check the value of the welcome message. Only display it if it is not empty
                string welcomeHeader = !string.IsNullOrEmpty(ConfigManager.Config.Server.Welcome) ? ConfigManager.Config.Server.Welcome : "Welcome to Asheron's Call!";
                string msg           = "To begin your training, speak to the Society Greeter. Walk up to the Society Greeter using the 'W' key, then double-click on her to initiate a conversation.";

                session.Network.EnqueueSend(new GameEventPopupString(session, $"{welcomeHeader}\n{msg}"));
            }

            LandblockManager.AddObject(session.Player, true);

            var motdString = PropertyManager.GetString("motd_string").Item;

            session.Network.EnqueueSend(new GameMessageSystemChat(motdString, ChatMessageType.Broadcast));
        }
Beispiel #2
0
        private static void DoPlayerEnterWorld(Session session, Character character, Biota playerBiota, PossessedBiotas possessedBiotas)
        {
            Player player;

            Player.HandleNoLogLandblock(playerBiota, out var playerLoggedInOnNoLogLandblock);

            var stripAdminProperties  = false;
            var addAdminProperties    = false;
            var addSentinelProperties = false;

            if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions)
            {
                if (session.AccessLevel <= AccessLevel.Advocate)                                                     // check for elevated characters
                {
                    if (playerBiota.WeenieType == WeenieType.Admin || playerBiota.WeenieType == WeenieType.Sentinel) // Downgrade weenie
                    {
                        character.IsPlussed    = false;
                        playerBiota.WeenieType = WeenieType.Creature;
                        stripAdminProperties   = true;
                    }
                }
                else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy)
                {
                    if (playerBiota.WeenieType == WeenieType.Creature || playerBiota.WeenieType == WeenieType.Admin) // Up/downgrade weenie
                    {
                        character.IsPlussed    = true;
                        playerBiota.WeenieType = WeenieType.Sentinel;
                        addSentinelProperties  = true;
                    }
                }
                else // Developers and Admins
                {
                    if (playerBiota.WeenieType == WeenieType.Creature || playerBiota.WeenieType == WeenieType.Sentinel) // Up/downgrade weenie
                    {
                        character.IsPlussed    = true;
                        playerBiota.WeenieType = WeenieType.Admin;
                        addAdminProperties     = true;
                    }
                }
            }

            if (playerBiota.WeenieType == WeenieType.Admin)
            {
                player = new Admin(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else if (playerBiota.WeenieType == WeenieType.Sentinel)
            {
                player = new Sentinel(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else
            {
                player = new Player(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }

            session.SetPlayer(player);

            if (stripAdminProperties) // continue stripping properties
            {
                player.CloakStatus = CloakStatus.Undef;
                player.Attackable  = true;
                player.SetProperty(PropertyBool.DamagedByCollisions, true);
                player.AdvocateLevel            = null;
                player.ChannelsActive           = null;
                player.ChannelsAllowed          = null;
                player.Invincible               = false;
                player.Cloaked                  = null;
                player.IgnoreHouseBarriers      = false;
                player.IgnorePortalRestrictions = false;
                player.SafeSpellComponents      = false;
                player.ReportCollisions         = true;


                player.ChangesDetected          = true;
                player.CharacterChangesDetected = true;
            }

            if (addSentinelProperties || addAdminProperties) // continue restoring properties to default
            {
                WorldObject weenie;

                if (addAdminProperties)
                {
                    weenie = Factories.WorldObjectFactory.CreateWorldObject(DatabaseManager.World.GetCachedWeenie("admin"), new ACE.Entity.ObjectGuid(ACE.Entity.ObjectGuid.Invalid.Full));
                }
                else
                {
                    weenie = Factories.WorldObjectFactory.CreateWorldObject(DatabaseManager.World.GetCachedWeenie("sentinel"), new ACE.Entity.ObjectGuid(ACE.Entity.ObjectGuid.Invalid.Full));
                }

                if (weenie != null)
                {
                    player.CloakStatus = CloakStatus.Off;
                    player.Attackable  = weenie.Attackable;
                    player.SetProperty(PropertyBool.DamagedByCollisions, false);
                    player.AdvocateLevel   = weenie.GetProperty(PropertyInt.AdvocateLevel);
                    player.ChannelsActive  = (Channel?)weenie.GetProperty(PropertyInt.ChannelsActive);
                    player.ChannelsAllowed = (Channel?)weenie.GetProperty(PropertyInt.ChannelsAllowed);
                    player.Invincible      = false;
                    player.Cloaked         = false;


                    player.ChangesDetected          = true;
                    player.CharacterChangesDetected = true;
                }
            }

            // If the client is missing a location, we start them off in the starter town they chose
            if (session.Player.Location == null)
            {
                if (session.Player.Instantiation != null)
                {
                    session.Player.Location = new Position(session.Player.Instantiation);
                }
                else
                {
                    session.Player.Location = new Position(0xA9B40019, 84, 7.1f, 94, 0, 0, -0.0784591f, 0.996917f);  // ultimate fallback
                }
            }

            var olthoiPlayerReturnedToLifestone = session.Player.IsOlthoiPlayer && character.TotalLogins >= 1 && session.Player.LoginAtLifestone;

            if (olthoiPlayerReturnedToLifestone)
            {
                session.Player.Location = new Position(session.Player.Sanctuary);
            }

            session.Player.PlayerEnterWorld();

            var success = LandblockManager.AddObject(session.Player, true);

            if (!success)
            {
                // send to lifestone, or fallback location
                var fixLoc = session.Player.Sanctuary ?? new Position(0xA9B40019, 84, 7.1f, 94, 0, 0, -0.0784591f, 0.996917f);

                log.Error($"WorldManager.DoPlayerEnterWorld: failed to spawn {session.Player.Name}, relocating to {fixLoc.ToLOCString()}");

                session.Player.Location = new Position(fixLoc);
                LandblockManager.AddObject(session.Player, true);

                var actionChain = new ActionChain();
                actionChain.AddDelaySeconds(5.0f);
                actionChain.AddAction(session.Player, () =>
                {
                    if (session != null && session.Player != null)
                    {
                        session.Player.Teleport(fixLoc);
                    }
                });
                actionChain.EnqueueChain();
            }

            // These warnings are set by DDD_InterrogationResponse
            if ((session.DatWarnCell || session.DatWarnLanguage || session.DatWarnPortal) && PropertyManager.GetBool("show_dat_warning").Item)
            {
                var msg     = PropertyManager.GetString("dat_warning_msg").Item;
                var chatMsg = new GameMessageSystemChat(msg, ChatMessageType.System);
                session.Network.EnqueueSend(chatMsg);
            }

            var popup_header  = PropertyManager.GetString("popup_header").Item;
            var popup_motd    = PropertyManager.GetString("popup_motd").Item;
            var popup_welcome = player.IsOlthoiPlayer ? PropertyManager.GetString("popup_welcome_olthoi").Item : PropertyManager.GetString("popup_welcome").Item;

            if (character.TotalLogins <= 1)
            {
                if (player.IsOlthoiPlayer)
                {
                    session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_welcome, popup_motd)));
                }
                else
                {
                    session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_header, popup_motd, popup_welcome)));
                }
            }
            else if (!string.IsNullOrEmpty(popup_motd))
            {
                session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_header, popup_motd)));
            }

            var info = "Welcome to Asheron's Call\n  powered by ACEmulator\n\nFor more information on commands supported by this server, type @acehelp\n";

            session.Network.EnqueueSend(new GameMessageSystemChat(info, ChatMessageType.Broadcast));

            var server_motd = PropertyManager.GetString("server_motd").Item;

            if (!string.IsNullOrEmpty(server_motd))
            {
                session.Network.EnqueueSend(new GameMessageSystemChat($"{server_motd}\n", ChatMessageType.Broadcast));
            }

            if (olthoiPlayerReturnedToLifestone)
            {
                session.Network.EnqueueSend(new GameMessageSystemChat("You have returned to the Olthoi Queen to serve the hive.", ChatMessageType.Broadcast));
            }
            else if (playerLoggedInOnNoLogLandblock) // see http://acpedia.org/wiki/Mount_Elyrii_Hive
            {
                session.Network.EnqueueSend(new GameMessageSystemChat("The currents of portal space cannot return you from whence you came. Your previous location forbids login.", ChatMessageType.Broadcast));
            }
        }
Beispiel #3
0
        private static void DoPlayerEnterWorld(Session session, Character character, Biota playerBiota, PossessedBiotas possessedBiotas)
        {
            Player player;

            Player.HandleNoLogLandblock(playerBiota);

            var stripAdminProperties  = false;
            var addAdminProperties    = false;
            var addSentinelProperties = false;

            if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions)
            {
                if (session.AccessLevel <= AccessLevel.Advocate)                                                               // check for elevated characters
                {
                    if (playerBiota.WeenieType == (int)WeenieType.Admin || playerBiota.WeenieType == (int)WeenieType.Sentinel) // Downgrade weenie
                    {
                        character.IsPlussed    = false;
                        playerBiota.WeenieType = (int)WeenieType.Creature;
                        stripAdminProperties   = true;
                    }
                }
                else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy)
                {
                    if (playerBiota.WeenieType == (int)WeenieType.Creature || playerBiota.WeenieType == (int)WeenieType.Admin) // Up/downgrade weenie
                    {
                        character.IsPlussed    = true;
                        playerBiota.WeenieType = (int)WeenieType.Sentinel;
                        addSentinelProperties  = true;
                    }
                }
                else // Developers and Admins
                {
                    if (playerBiota.WeenieType == (int)WeenieType.Creature || playerBiota.WeenieType == (int)WeenieType.Sentinel) // Up/downgrade weenie
                    {
                        character.IsPlussed    = true;
                        playerBiota.WeenieType = (int)WeenieType.Admin;
                        addAdminProperties     = true;
                    }
                }
            }

            if (playerBiota.WeenieType == (int)WeenieType.Admin)
            {
                player = new Admin(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else if (playerBiota.WeenieType == (int)WeenieType.Sentinel)
            {
                player = new Sentinel(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else
            {
                player = new Player(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }

            session.SetPlayer(player);

            if (stripAdminProperties) // continue stripping properties
            {
                player.CloakStatus = null;
                player.Attackable  = true;
                player.SetProperty(ACE.Entity.Enum.Properties.PropertyBool.DamagedByCollisions, true);
                player.AdvocateLevel            = null;
                player.ChannelsActive           = null;
                player.ChannelsAllowed          = null;
                player.Invincible               = false;
                player.Cloaked                  = null;
                player.IgnoreHouseBarriers      = false;
                player.IgnorePortalRestrictions = false;
                player.SafeSpellComponents      = false;


                player.ChangesDetected          = true;
                player.CharacterChangesDetected = true;
            }

            if (addSentinelProperties || addAdminProperties) // continue restoring properties to default
            {
                WorldObject weenie;

                if (addAdminProperties)
                {
                    weenie = Factories.WorldObjectFactory.CreateWorldObject(DatabaseManager.World.GetCachedWeenie("admin"), new ACE.Entity.ObjectGuid(ACE.Entity.ObjectGuid.Invalid.Full));
                }
                else
                {
                    weenie = Factories.WorldObjectFactory.CreateWorldObject(DatabaseManager.World.GetCachedWeenie("sentinel"), new ACE.Entity.ObjectGuid(ACE.Entity.ObjectGuid.Invalid.Full));
                }

                if (weenie != null)
                {
                    player.CloakStatus = CloakStatus.Off;
                    player.Attackable  = weenie.Attackable;
                    player.SetProperty(ACE.Entity.Enum.Properties.PropertyBool.DamagedByCollisions, false);
                    player.AdvocateLevel   = weenie.GetProperty(ACE.Entity.Enum.Properties.PropertyInt.AdvocateLevel);
                    player.ChannelsActive  = (Channel?)weenie.GetProperty(ACE.Entity.Enum.Properties.PropertyInt.ChannelsActive);
                    player.ChannelsAllowed = (Channel?)weenie.GetProperty(ACE.Entity.Enum.Properties.PropertyInt.ChannelsAllowed);
                    player.Invincible      = false;
                    player.Cloaked         = false;


                    player.ChangesDetected          = true;
                    player.CharacterChangesDetected = true;
                }
            }

            // If the client is missing a location, we start them off in the starter town they chose
            if (session.Player.Location == null)
            {
                if (session.Player.Instantiation != null)
                {
                    session.Player.Location = new Position(session.Player.Instantiation);
                }
                else
                {
                    session.Player.Location = new Position(0xA9B40019, 84, 7.1f, 94, 0, 0, -0.0784591f, 0.996917f); // ultimate fallback;
                }
            }

            session.Player.PlayerEnterWorld();

            var success = LandblockManager.AddObject(session.Player, true);

            if (!success)
            {
                // send to lifestone, or fallback location
                var fixLoc = session.Player.Sanctuary ?? new Position(0xA9B40019, 84, 7.1f, 94, 0, 0, -0.0784591f, 0.996917f);

                log.Error($"WorldManager.DoPlayerEnterWorld: failed to spawn {session.Player.Name}, relocating to {fixLoc.ToLOCString()}");

                session.Player.Location = new Position(fixLoc);
                LandblockManager.AddObject(session.Player, true);

                var actionChain = new ActionChain();
                actionChain.AddDelaySeconds(5.0f);
                actionChain.AddAction(session.Player, () =>
                {
                    if (session != null && session.Player != null)
                    {
                        session.Player.Teleport(fixLoc);
                    }
                });
                actionChain.EnqueueChain();
            }

            var popup_header  = PropertyManager.GetString("popup_header").Item;
            var popup_motd    = PropertyManager.GetString("popup_motd").Item;
            var popup_welcome = PropertyManager.GetString("popup_welcome").Item;

            if (character.TotalLogins <= 1)
            {
                session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_header, popup_motd, popup_welcome)));
            }
            else if (!string.IsNullOrEmpty(popup_motd))
            {
                session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_header, popup_motd)));
            }

            var info = "Welcome to Asheron's Call\n  powered by ACEmulator\n\nFor more information on commands supported by this server, type @acehelp\n";

            session.Network.EnqueueSend(new GameMessageSystemChat(info, ChatMessageType.Broadcast));

            var server_motd = PropertyManager.GetString("server_motd").Item;

            if (!string.IsNullOrEmpty(server_motd))
            {
                session.Network.EnqueueSend(new GameMessageSystemChat($"{server_motd}\n", ChatMessageType.Broadcast));
            }
        }
Beispiel #4
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Network commands come from their own listener threads, and are queued in world objects
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");
            double lastTick = 0d;

            WorldActive = true;
            var worldTickTimer = new Stopwatch();

            while (!pendingWorldStop)
            {
                worldTickTimer.Restart();

                // Handle time-based timeouts
                DelayManager.RunActions();

                // Sequences of update thread:
                // Update positions based on new tick
                // TODO(ddevec): Physics here
                IEnumerable <WorldObject> movedObjects = FakePhysics(PortalYearTicks);

                // Do any pre-calculated landblock transfers --
                foreach (WorldObject wo in movedObjects)
                {
                    // If it was picked up, or moved
                    // NOTE: The object's Location can now be null, if a player logs out, or an item is picked up
                    if (wo.Location != null && wo.Location.LandblockId != wo.CurrentLandblock.Id)
                    {
                        // NOTE: We are moving the objects on behalf of the physics
                        LandblockManager.RelocateObjectForPhysics(wo);
                    }
                }

                // FIXME(ddevec): This O(n^2) tracking loop is a remenant of the old structure -- we should probably come up with a more efficient tracking scheme
                Parallel.ForEach(movedObjects, mo =>
                {
                    // detect all world objects in ghost range
                    List <WorldObject> woproxghost = new List <WorldObject>();
                    woproxghost.AddRange(mo.CurrentLandblock.GetWorldObjectsInRangeForPhysics(mo, Landblock.MaxObjectGhostRange));

                    // for all objects in range of this moving object or in ghost range of moving object update them.
                    Parallel.ForEach(woproxghost, gwo =>
                    {
                        if (mo.Guid.IsPlayer())
                        {
                            // if world object is in active zone then.
                            if (gwo.Location.SquaredDistanceTo(mo.Location) <= Landblock.MaxObjectRange * Landblock.MaxObjectRange)
                            {
                                // if world object is in active zone.
                                if (!(mo as Player).GetTrackedObjectGuids().Contains(gwo.Guid))
                                {
                                    (mo as Player).TrackObject(gwo);
                                }
                            }
                            // if world object is in ghost zone and outside of active zone
                            else
                            {
                                if ((mo as Player).GetTrackedObjectGuids().Contains(gwo.Guid))
                                {
                                    (mo as Player).StopTrackingObject(gwo, false);
                                }
                            }
                        }
                    });
                });

                // Process between landblock object motions sequentially
                // Currently only used for picking items up off a landblock
                MotionQueue.RunActions();

                // Now, update actions within landblocks
                //   This is responsible for updating all "actors" residing within the landblock.
                //   Objects and landblocks are "actors"
                //   "actors" decide if they want to read/modify their own state (set desired velocity), move-to positions, move items, read vitals, etc
                // N.B. -- Broadcasts are enqueued for sending at the end of the landblock's action time
                // FIXME(ddevec): Goal is to eventually migrate to an "Act" function of the LandblockManager ActiveLandblocks
                //    Inactive landblocks will be put on TimeoutManager queue for timeout killing
                ActionQueue.RunActions();

                // Handles sending out all per-landblock broadcasts -- This may rework when we rework tracking -- tbd
                BroadcastQueue.RunActions();

                // XXX(ddevec): Should this be its own step in world-update thread?
                sessionLock.EnterReadLock();
                try
                {
                    // Send the current time ticks to allow sessions to declare themselves bad
                    Parallel.ForEach(sessions, s => s.Update(lastTick, DateTime.UtcNow.Ticks));
                }
                finally
                {
                    sessionLock.ExitReadLock();
                }

                // Removes sessions in the NetworkTimeout state, incuding sessions that have reached a timeout limit.
                var deadSessions = sessions.FindAll(s => s.State == Network.Enum.SessionState.NetworkTimeout);
                if (deadSessions.Count > 0)
                {
                    Parallel.ForEach(deadSessions, RemoveSession);
                }

                Thread.Sleep(1);

                lastTick         = (double)worldTickTimer.ElapsedTicks / Stopwatch.Frequency;
                PortalYearTicks += lastTick;
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
Beispiel #5
0
        /// <summary>
        /// Threaded task created when performing a server shutdown
        /// </summary>
        private static void ShutdownServer()
        {
            var shutdownTime = DateTime.UtcNow.AddSeconds(ShutdownInterval);

            ShutdownTime = shutdownTime;

            var lastNoticeTime = DateTime.UtcNow;

            // wait for shutdown interval to expire
            while (shutdownTime != DateTime.MinValue && shutdownTime >= DateTime.UtcNow)
            {
                // this allows the server shutdown to be canceled
                if (!ShutdownInitiated)
                {
                    // reset shutdown details
                    string shutdownText = $"The server has canceled the shutdown procedure @ {DateTime.UtcNow} UTC";
                    log.Info(shutdownText);

                    // special text
                    foreach (var player in PlayerManager.GetAllOnline())
                    {
                        player.Session.WorldBroadcast(shutdownText);
                    }

                    // break function
                    return;
                }

                lastNoticeTime = NotifyPlayersOfPendingShutdown(lastNoticeTime, shutdownTime.AddSeconds(1));

                Thread.Sleep(10);
            }

            PropertyManager.ResyncVariables();
            PropertyManager.StopUpdating();

            log.Debug("Logging off all players...");

            // logout each player
            foreach (var player in PlayerManager.GetAllOnline())
            {
                player.Session.LogOffPlayer();
            }

            log.Info("Waiting for all players to log off...");

            // wait 10 seconds for log-off
            while (PlayerManager.GetAllOnline().Count > 0)
            {
                Thread.Sleep(10);
            }

            log.Debug("Adding all landblocks to destruction queue...");

            // Queue unloading of all the landblocks
            // The actual unloading will happen in WorldManager.UpdateGameWorld
            LandblockManager.AddAllActiveLandblocksToDestructionQueue();

            log.Info("Waiting for all active landblocks to unload...");

            while (LandblockManager.GetLoadedLandblocks().Count > 0)
            {
                Thread.Sleep(10);
            }

            log.Debug("Stopping world...");

            // Disabled thread update loop
            WorldManager.StopWorld();

            log.Info("Waiting for world to stop...");

            // Wait for world to end
            while (WorldManager.WorldActive)
            {
                Thread.Sleep(10);
            }

            //log.Info("Saving OfflinePlayers that have unsaved changes...");
            //PlayerManager.SaveOfflinePlayersWithChanges();

            log.Info("Waiting for database queue to empty...");

            // Wait for the database queue to empty
            while (DatabaseManager.Shard.QueueCount > 0)
            {
                Thread.Sleep(10);
            }

            // Write exit to console/log
            log.Info($"Exiting at {DateTime.UtcNow}");

            // System exit
            Environment.Exit(Environment.ExitCode);
        }
Beispiel #6
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Database I/O is handled in its own thread.
        ///  - Network commands come from their own listener threads, and are queued for each sessions which are then processed here.
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");

            double lastTickDuration = 0d;

            WorldActive = true;
            var worldTickTimer = new Stopwatch();

            while (!pendingWorldStop)
            {
                worldTickTimer.Restart();

                /*
                 * When it comes to thread safety for Landblocks and WorldObjects, ACE makes the following assumptions:
                 *
                 * Inbound ClientMessages and GameActions are handled on the main UpdateWorld thread.
                 * - These actions may load Landblocks and modify other WorldObjects safely.
                 *
                 * PlayerEnterWorld queue is run on the main UpdateWorld thread.
                 * - These actions may load Landblocks and modify other WorldObjects safely.
                 *
                 * Landblock Groups (calculated by LandblockManager) can be processed in parallel.
                 *
                 * Adjacent Landblocks will always be run on the same thread.
                 *
                 * Non-adjacent landblocks might be run on different threads.
                 * - If two non-adjacent landblocks both touch the same landblock, and that landblock is active, they will be run on the same thread.
                 *
                 * Database results are returned from a task spawned in SerializedShardDatabase (via callback).
                 * - Minimal processing should be done from the callback. Return as quickly as possible to let the database thread do database work.
                 * - The processing of these results should be queued to an ActionQueue
                 *
                 * The only cases where it's acceptable for to create a new Task, Thread or Parallel loop are the following:
                 * - Every scenario must be one where you don't care about breaking ACE
                 * - DeveloperCommand Handlers
                 *
                 * TODO: We need a thread safe way to handle object transitions between distant landblocks
                 */

                InboundClientMessageQueue.RunActions();

                playerEnterWorldQueue.RunActions();

                DelayManager.RunActions();

                // update positions through physics engine
                var movedObjects = HandlePhysics(PortalYearTicks);

                // iterate through objects that have changed landblocks
                foreach (var movedObject in movedObjects)
                {
                    // NOTE: The object's Location can now be null, if a player logs out, or an item is picked up
                    if (movedObject.Location == null)
                    {
                        continue;
                    }

                    // assume adjacency move here?
                    LandblockManager.RelocateObjectForPhysics(movedObject, true);
                }

                // Tick all of our Landblocks and WorldObjects
                var activeLandblocks = LandblockManager.GetActiveLandblocks();

                foreach (var landblock in activeLandblocks)
                {
                    landblock.Tick(lastTickDuration, Time.GetUnixTime());
                }

                // clean up inactive landblocks
                LandblockManager.UnloadLandblocks();

                // Session Maintenance
                int sessionCount;

                sessionLock.EnterUpgradeableReadLock();
                try
                {
                    sessionCount = sessions.Count;

                    // The session tick processes all inbound GameAction messages
                    foreach (var s in sessions)
                    {
                        s.Tick(lastTickDuration);
                    }

                    // Send the current time ticks to allow sessions to declare themselves bad
                    Parallel.ForEach(sessions, s => s.TickInParallel(lastTickDuration));

                    // Removes sessions in the NetworkTimeout state, incuding sessions that have reached a timeout limit.
                    var deadSessions = sessions.FindAll(s => s.State == Network.Enum.SessionState.NetworkTimeout);

                    foreach (var session in deadSessions)
                    {
                        log.Info($"client {session.Account} dropped");
                        RemoveSession(session);
                    }
                }
                finally
                {
                    sessionLock.ExitUpgradeableReadLock();
                }

                Thread.Sleep(sessionCount == 0 ? 10 : 1); // Relax the CPU if no sessions are connected

                lastTickDuration = worldTickTimer.Elapsed.TotalSeconds;
                PortalYearTicks += lastTickDuration;
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
Beispiel #7
0
        /// <summary>
        /// Threaded task created when performing a server shutdown
        /// </summary>
        private static void ShutdownServer()
        {
            var shutdownTime = DateTime.UtcNow.AddSeconds(ShutdownInterval);

            ShutdownTime = shutdownTime;

            var lastNoticeTime = DateTime.UtcNow;

            // wait for shutdown interval to expire
            while (shutdownTime != DateTime.MinValue && shutdownTime >= DateTime.UtcNow)
            {
                // this allows the server shutdown to be canceled
                if (!ShutdownInitiated)
                {
                    // reset shutdown details
                    string shutdownText = $"The server has canceled the shutdown procedure @ {DateTime.UtcNow} UTC";
                    log.Info(shutdownText);

                    // special text
                    foreach (var player in PlayerManager.GetAllOnline())
                    {
                        player.Session.WorldBroadcast(shutdownText);
                    }

                    // break function
                    return;
                }

                lastNoticeTime = NotifyPlayersOfPendingShutdown(lastNoticeTime, shutdownTime.AddSeconds(1));

                Thread.Sleep(10);
            }

            ShutdownInProgress = true;

            PropertyManager.ResyncVariables();
            PropertyManager.StopUpdating();

            log.Debug("Logging off all players...");

            // logout each player
            foreach (var player in PlayerManager.GetAllOnline())
            {
                player.Session.LogOffPlayer(true);
            }

            // Wait for all players to log out
            var logUpdateTS = DateTime.MinValue;
            int playerCount;

            while ((playerCount = PlayerManager.GetOnlineCount()) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {playerCount} player{(playerCount > 1 ? "s" : "")} to log off...");
                Thread.Sleep(10);
            }

            log.Debug("Disconnecting all sessions...");

            // disconnect each session
            NetworkManager.DisconnectAllSessionsForShutdown();

            // Wait for all sessions to drop out
            logUpdateTS = DateTime.MinValue;
            int sessionCount;

            while ((sessionCount = NetworkManager.GetSessionCount()) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {sessionCount} session{(sessionCount > 1 ? "s" : "")} to disconnect...");
                Thread.Sleep(10);
            }

            log.Debug("Adding all landblocks to destruction queue...");

            // Queue unloading of all the landblocks
            // The actual unloading will happen in WorldManager.UpdateGameWorld
            LandblockManager.AddAllActiveLandblocksToDestructionQueue();

            // Wait for all landblocks to unload
            logUpdateTS = DateTime.MinValue;
            int landblockCount;

            while ((landblockCount = LandblockManager.GetLoadedLandblocks().Count) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {landblockCount} loaded landblock{(landblockCount > 1 ? "s" : "")} to unload...");
                Thread.Sleep(10);
            }

            log.Debug("Stopping world...");

            // Disabled thread update loop
            WorldManager.StopWorld();

            // Wait for world to end
            logUpdateTS = DateTime.MinValue;
            while (WorldManager.WorldActive)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, "Waiting for world to stop...");
                Thread.Sleep(10);
            }

            log.Info("Saving OfflinePlayers that have unsaved changes...");
            PlayerManager.SaveOfflinePlayersWithChanges();

            // Wait for the database queue to empty
            logUpdateTS = DateTime.MinValue;
            int shardQueueCount;

            while ((shardQueueCount = DatabaseManager.Shard.QueueCount) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for database queue ({shardQueueCount}) to empty...");
                Thread.Sleep(10);
            }

            // Write exit to console/log
            log.Info($"Exiting at {DateTime.UtcNow}");

            // System exit
            Environment.Exit(Environment.ExitCode);
        }
Beispiel #8
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Network commands come from their own listener threads, and are queued in world objects
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");
            double lastTick = 0d;

            WorldActive = true;
            var worldTickTimer = new Stopwatch();

            while (!pendingWorldStop)
            {
                worldTickTimer.Restart();

                // handle time-based actions
                DelayManager.RunActions();

                // update positions through physics engine
                var movedObjects = HandlePhysics(PortalYearTicks);

                // iterate through objects that have changed landblocks
                foreach (var movedObject in movedObjects)
                {
                    // NOTE: The object's Location can now be null, if a player logs out, or an item is picked up
                    if (movedObject.Location == null)
                    {
                        continue;
                    }

                    // assume adjacency move here?
                    LandblockManager.RelocateObjectForPhysics(movedObject, true);
                }

                InboundMessageQueue.RunActions();

                // Process between landblock object motions sequentially
                // Currently only used for picking items up off a landblock
                LandblockMotionQueue.RunActions();

                // Now, update actions within landblocks
                //   This is responsible for updating all "actors" residing within the landblock.
                //   Objects and landblocks are "actors"
                //   "actors" decide if they want to read/modify their own state (set desired velocity), move-to positions, move items, read vitals, etc
                // N.B. -- Broadcasts are enqueued for sending at the end of the landblock's action time
                // FIXME(ddevec): Goal is to eventually migrate to an "Act" function of the LandblockManager ActiveLandblocks
                //    Inactive landblocks will be put on TimeoutManager queue for timeout killing
                LandblockActionQueue.RunActions();

                // Handles sending out all per-landblock broadcasts -- This may rework when we rework tracking -- tbd
                LandblockBroadcastQueue.RunActions();

                // XXX(ddevec): Should this be its own step in world-update thread?
                sessionLock.EnterReadLock();
                try
                {
                    // Send the current time ticks to allow sessions to declare themselves bad
                    Parallel.ForEach(sessions, s => s.Update(lastTick, DateTime.UtcNow.Ticks));
                }
                finally
                {
                    sessionLock.ExitReadLock();
                }

                // Removes sessions in the NetworkTimeout state, incuding sessions that have reached a timeout limit.
                var deadSessions = sessions.FindAll(s => s.State == Network.Enum.SessionState.NetworkTimeout);
                if (deadSessions.Count > 0)
                {
                    Parallel.ForEach(deadSessions, RemoveSession);
                }

                Thread.Sleep(1);

                lastTick         = (double)worldTickTimer.ElapsedTicks / Stopwatch.Frequency;
                PortalYearTicks += lastTick;

                // clean up inactive landblocks
                LandblockManager.UnloadLandblocks();
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
Beispiel #9
0
        private static void DoPlayerEnterWorld(Session session, Character character, Biota playerBiota, PossessedBiotas possessedBiotas)
        {
            Player player;

            Player.HandleNoLogLandblock(playerBiota);

            var stripAdminProperties  = false;
            var addAdminProperties    = false;
            var addSentinelProperties = false;

            if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions)
            {
                if (session.AccessLevel <= AccessLevel.Advocate)                                                               // check for elevated characters
                {
                    if (playerBiota.WeenieType == (int)WeenieType.Admin || playerBiota.WeenieType == (int)WeenieType.Sentinel) // Downgrade weenie
                    {
                        character.IsPlussed    = false;
                        playerBiota.WeenieType = (int)WeenieType.Creature;
                        stripAdminProperties   = true;
                    }
                }
                else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy)
                {
                    if (playerBiota.WeenieType == (int)WeenieType.Creature || playerBiota.WeenieType == (int)WeenieType.Admin) // Up/downgrade weenie
                    {
                        character.IsPlussed    = true;
                        playerBiota.WeenieType = (int)WeenieType.Sentinel;
                        addSentinelProperties  = true;
                    }
                }
                else // Developers and Admins
                {
                    if (playerBiota.WeenieType == (int)WeenieType.Creature || playerBiota.WeenieType == (int)WeenieType.Sentinel) // Up/downgrade weenie
                    {
                        character.IsPlussed    = true;
                        playerBiota.WeenieType = (int)WeenieType.Admin;
                        addAdminProperties     = true;
                    }
                }
            }

            if (playerBiota.WeenieType == (int)WeenieType.Admin)
            {
                player = new Admin(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else if (playerBiota.WeenieType == (int)WeenieType.Sentinel)
            {
                player = new Sentinel(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }
            else
            {
                player = new Player(playerBiota, possessedBiotas.Inventory, possessedBiotas.WieldedItems, character, session);
            }

            session.SetPlayer(player);

            if (stripAdminProperties) // continue stripping properties
            {
                player.CloakStatus = null;
                player.Attackable  = true;
                player.SetProperty(ACE.Entity.Enum.Properties.PropertyBool.DamagedByCollisions, true);
                player.AdvocateLevel   = null;
                player.ChannelsActive  = null;
                player.ChannelsAllowed = null;
                player.Invincible      = null;
                player.Cloaked         = null;


                player.ChangesDetected          = true;
                player.CharacterChangesDetected = true;
            }

            if (addSentinelProperties || addAdminProperties) // continue restoring properties to default
            {
                WorldObject weenie;

                if (addAdminProperties)
                {
                    weenie = Factories.WorldObjectFactory.CreateWorldObject(DatabaseManager.World.GetCachedWeenie("admin"), new ACE.Entity.ObjectGuid(ACE.Entity.ObjectGuid.Invalid.Full)) as Admin;
                }
                else
                {
                    weenie = Factories.WorldObjectFactory.CreateWorldObject(DatabaseManager.World.GetCachedWeenie("sentinel"), new ACE.Entity.ObjectGuid(ACE.Entity.ObjectGuid.Invalid.Full)) as Sentinel;
                }

                if (weenie != null)
                {
                    player.CloakStatus = CloakStatus.Off;
                    player.Attackable  = weenie.Attackable;
                    player.SetProperty(ACE.Entity.Enum.Properties.PropertyBool.DamagedByCollisions, false);
                    player.AdvocateLevel   = weenie.GetProperty(ACE.Entity.Enum.Properties.PropertyInt.AdvocateLevel);
                    player.ChannelsActive  = (Channel?)weenie.GetProperty(ACE.Entity.Enum.Properties.PropertyInt.ChannelsActive);
                    player.ChannelsAllowed = (Channel?)weenie.GetProperty(ACE.Entity.Enum.Properties.PropertyInt.ChannelsAllowed);
                    player.Invincible      = false;
                    player.Cloaked         = false;


                    player.ChangesDetected          = true;
                    player.CharacterChangesDetected = true;
                }
            }

            // If the client is missing a location, we start them off in the starter dungeon
            if (session.Player.Location == null)
            {
                if (session.Player.Instantiation != null)
                {
                    session.Player.Location = new Position(session.Player.Instantiation);
                }
                else
                {
                    session.Player.Location = new Position(2349072813, 12.3199f, -28.482f, 0.0049999995f, 0.0f, 0.0f, -0.9408059f, -0.3389459f);
                }
            }

            session.Player.PlayerEnterWorld();

            LandblockManager.AddObject(session.Player, true);

            var popup_header  = PropertyManager.GetString("popup_header").Item;
            var popup_motd    = PropertyManager.GetString("popup_motd").Item;
            var popup_welcome = PropertyManager.GetString("popup_welcome").Item;

            if (character.TotalLogins <= 1)
            {
                session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_header, popup_motd, popup_welcome)));
            }
            else if (!string.IsNullOrEmpty(popup_motd))
            {
                session.Network.EnqueueSend(new GameEventPopupString(session, AppendLines(popup_header, popup_motd)));
            }

            var info = "Welcome to Asheron's Call\n  powered by ACEmulator\n\nFor more information on commands supported by this server, type @acehelp\n";

            session.Network.EnqueueSend(new GameMessageSystemChat(info, ChatMessageType.Broadcast));

            var server_motd = PropertyManager.GetString("server_motd").Item;

            if (!string.IsNullOrEmpty(server_motd))
            {
                session.Network.EnqueueSend(new GameMessageSystemChat($"{server_motd}\n", ChatMessageType.Broadcast));
            }
        }
Beispiel #10
0
        /// <summary>
        /// Threaded task created when performing a server shutdown
        /// </summary>
        private static void ShutdownServer()
        {
            var shutdownTime = DateTime.UtcNow.AddSeconds(ShutdownInterval);

            ShutdownTime = shutdownTime;

            var lastNoticeTime = DateTime.UtcNow;

            // wait for shutdown interval to expire
            while (shutdownTime != DateTime.MinValue && shutdownTime >= DateTime.UtcNow)
            {
                // this allows the server shutdown to be canceled
                if (!ShutdownInitiated)
                {
                    // reset shutdown details
                    string shutdownText = $"The server shut down has been cancelled @ {DateTime.Now} ({DateTime.UtcNow} UTC)";
                    log.Info(shutdownText);

                    // special text
                    foreach (var player in PlayerManager.GetAllOnline())
                    {
                        player.Session.WorldBroadcast($"Broadcast from System> ATTENTION - This Asheron's Call Server shut down has been cancelled.");
                    }

                    // break function
                    return;
                }

                lastNoticeTime = NotifyPlayersOfPendingShutdown(lastNoticeTime, shutdownTime.AddSeconds(1));

                Thread.Sleep(10);
            }

            ShutdownInProgress = true;

            PropertyManager.ResyncVariables();
            PropertyManager.StopUpdating();

            WorldManager.EnqueueAction(new ActionEventDelegate(() =>
            {
                log.Debug("Logging off all players...");

                // logout each player
                foreach (var player in PlayerManager.GetAllOnline())
                {
                    player.Session.LogOffPlayer(true);
                }
            }));

            // Wait for all players to log out
            var logUpdateTS = DateTime.MinValue;
            int playerCount;
            var playerLogoffStart = DateTime.UtcNow;

            while ((playerCount = PlayerManager.GetOnlineCount()) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {playerCount} player{(playerCount > 1 ? "s" : "")} to log off...");
                Thread.Sleep(10);
                if (playerCount > 0 && DateTime.UtcNow - playerLogoffStart > TimeSpan.FromMinutes(5))
                {
                    playerLogoffStart = DateTime.UtcNow;
                    log.Warn($"5 minute log off failsafe reached and there are {playerCount} player{(playerCount > 1 ? "s" : "")} still online.");
                    foreach (var player in PlayerManager.GetAllOnline())
                    {
                        log.Warn($"Player {player.Name} (0x{player.Guid}) appears to be stuck in world and unable to log off normally. Requesting Forced Logoff...");
                        player.ForcedLogOffRequested = true;
                        player.ForceLogoff();
                    }
                }
            }

            WorldManager.EnqueueAction(new ActionEventDelegate(() =>
            {
                log.Debug("Disconnecting all sessions...");

                // disconnect each session
                NetworkManager.DisconnectAllSessionsForShutdown();
            }));

            // Wait for all sessions to drop out
            logUpdateTS = DateTime.MinValue;
            int sessionCount;

            while ((sessionCount = NetworkManager.GetAuthenticatedSessionCount()) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {sessionCount} authenticated session{(sessionCount > 1 ? "s" : "")} to disconnect...");
                Thread.Sleep(10);
            }

            log.Debug("Adding all landblocks to destruction queue...");

            // Queue unloading of all the landblocks
            // The actual unloading will happen in WorldManager.UpdateGameWorld
            LandblockManager.AddAllActiveLandblocksToDestructionQueue();

            // Wait for all landblocks to unload
            logUpdateTS = DateTime.MinValue;
            int landblockCount;

            while ((landblockCount = LandblockManager.GetLoadedLandblocks().Count) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {landblockCount} loaded landblock{(landblockCount > 1 ? "s" : "")} to unload...");
                Thread.Sleep(10);
            }

            log.Debug("Stopping world...");

            // Disabled thread update loop
            WorldManager.StopWorld();

            // Wait for world to end
            logUpdateTS = DateTime.MinValue;
            while (WorldManager.WorldActive)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, "Waiting for world to stop...");
                Thread.Sleep(10);
            }

            log.Info("Saving OfflinePlayers that have unsaved changes...");
            PlayerManager.SaveOfflinePlayersWithChanges();

            // Wait for the database queue to empty
            logUpdateTS = DateTime.MinValue;
            int shardQueueCount;

            while ((shardQueueCount = DatabaseManager.Shard.QueueCount) > 0)
            {
                logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for database queue ({shardQueueCount}) to empty...");
                Thread.Sleep(10);
            }

            // Write exit to console/log
            log.Info($"Exiting at {DateTime.UtcNow}");

            // System exit
            Environment.Exit(Environment.ExitCode);
        }