public void RequestRoommate(VM vm, uint avatarID, int mode, byte permissions) { //0 = initiate. 1 = accept. 2 = reject. //we have the "initiate" step so that users are reminded if they somehow dc before the transaction completes. //users will be reminded of their in-progress roommate request every time they log in or leave a lot. (potentially check on most server actions) var lotID = Context.DbId; Host.InBackground(() => { //TODO: use results in meaningful fashion using (var db = DAFactory.Get()) { bool queryResult; switch (mode) { case 1: queryResult = db.Roommates.Create(new DbRoommate() { avatar_id = avatarID, lot_id = lotID, is_pending = 0, permissions_level = 0 }); if (queryResult) { vm.ForwardCommand(new VMChangePermissionsCmd() { TargetUID = avatarID, Level = VMTSOAvatarPermissions.Roommate, Verified = true }); } break; //the following code enables pending requests, like in the original game. I decided they only really make sense for requests initiated from city. /* * case 0: * queryResult = db.Roommates.Create(new DbRoommate() * { * avatar_id = avatarID, * lot_id = lotID, * is_pending = 1, * permissions_level = 0 * }); * // dont do anything if we fail. I can see on-lot roommate requests having precedence over off-lot ones though, * // so forcing a wipe of old pending requests might make sense. * break; * case 1: * //accept * queryResult = db.Roommates.AcceptRoommateRequest(avatarID, lotID); * //if it worked, tell everyone in the lot that there's a new roommate. * if (queryResult) * { * vm.ForwardCommand(new VMChangePermissionsCmd() * { * TargetUID = avatarID, * Level = VMTSOAvatarPermissions.Roommate, * Verified = true * }); * } * //todo: error feedback. I think the global call is meant to block for an answer and possibly return false. * break; * case 2: * queryResult = db.Roommates.RemoveRoommate(avatarID, lotID) > 0; * break; */ case 3: //FSO specific mode: switch permissions. queryResult = db.Roommates.UpdatePermissionsLevel(avatarID, lotID, permissions); if (queryResult) { vm.ForwardCommand(new VMChangePermissionsCmd() { TargetUID = avatarID, Level = (permissions == 0)?VMTSOAvatarPermissions.Roommate:VMTSOAvatarPermissions.BuildBuyRoommate, Verified = true }); } break; } Host.SyncRoommates(); } }); }
/// <summary> /// Load and initialize everything to start up the lot /// </summary> public void Run() { try { try { ResetVM(); } catch (Exception e) { LOG.Info("LOT " + Context.DbId + " LOAD EXECPTION:" + e.ToString()); Host.Shutdown(); return; } LOG.Info("Starting to host lot with dbid = " + Context.DbId); Host.SetOnline(true); var timeKeeper = new Stopwatch(); //todo: smarter timing timeKeeper.Start(); long lastTick = 0; LotSaveTicker = LOT_SAVE_PERIOD; AvatarSaveTicker = AVATAR_SAVE_PERIOD; while (true) { bool noRemainingUsers = ClientCount == 0; lastTick++; //sometimes avatars can be killed immediately after their kill timer starts (this frame will run the leave lot interaction) //this works around that possibility. var preTickAvatars = Lot.Context.ObjectQueries.AvatarsByPersist.Values.Select(x => x).ToList(); var noRoomies = !(preTickAvatars.Any(x => ((VMTSOAvatarState)x.TSOState).Permissions > VMTSOAvatarPermissions.Visitor)) && LotPersist.admit_mode < 4; try { Lot.Tick(); } catch (Exception e) { //something bad happened. not entirely sure how we should deal with this yet LOG.Error("VM ERROR: " + e.StackTrace); Host.Shutdown(); return; } if (noRoomies) { //no roommates are here, so all visitors must be kicked out. foreach (var avatar in preTickAvatars) { if (avatar.KillTimeout == -1) { avatar.UserLeaveLot(); } VMDriver.DropAvatar(avatar); } } if (noRemainingUsers) { if (TimeToShutdown == -1) { //lot shuts down 20 seconds after everyone leaves //if we're doing a cleanup action, it closes immediately TimeToShutdown = (Context.Action == ClaimAction.LOT_CLEANUP) ? 1 : TICKRATE * 20; } else { if (--TimeToShutdown == 0 || (ShuttingDown && TimeToShutdown < (TICKRATE * 20 - 10))) { Shutdown(); return; //kill the lot } } } else if (!noRoomies && TimeToShutdown != -1) { TimeToShutdown = -1; } if (--LotSaveTicker <= 0) { SaveRing(); LotSaveTicker = LOT_SAVE_PERIOD; Host.UpdateActiveVisitRecords(); } var beingKilled = preTickAvatars.Where(x => x.KillTimeout == 1); if (beingKilled.Count() > 0) { //avatars that are being killed could die before their user disconnects. It's important to save them immediately. LOG.Info("Avatar Kill Save"); SaveAvatars(beingKilled, true); } foreach (var avatar in Lot.Context.ObjectQueries.AvatarsByPersist) { if (avatar.Value.KillTimeout == 1) { //this avatar has begun being killed. Save them immediately. SaveAvatar(avatar.Value); } } if (--AvatarSaveTicker <= 0) { //save all avatars SaveAvatars(Lot.Context.ObjectQueries.Avatars.Cast <VMAvatar>(), false); AvatarSaveTicker = AVATAR_SAVE_PERIOD; } lock (SessionsToRelease) { //save avatar state, then release their avatar claims afterwards. //SaveAvatars(SessionsToRelease.Select(x => Lot.GetAvatarByPersist(x.AvatarId)), true); //todo: is this performed by the fact that we started the persist save above? foreach (var session in SessionsToRelease) { LOG.Info("Avatar Session Release"); Host.ReleaseAvatarClaim(session); } SessionsToRelease.Clear(); } lock (LotThreadActions) { while (LotThreadActions.Count > 0) { LotThreadActions.Dequeue()(); } } if (--KeepAliveTicker <= 0) { Host.InBackground(null); KeepAliveTicker = KEEP_ALIVE_PERIOD; } Thread.Sleep((int)Math.Max(0, (((lastTick + 1) * 1000) / TICKRATE) - timeKeeper.ElapsedMilliseconds)); } } catch (Exception e) { LOG.Info("Fatal exception on lot " + Context.DbId + ":" + e.ToString()); Host.Shutdown(); return; } }