/// <summary> /// Tasks we handle are: /// 1) Asking a server to host the lot /// 2) Telling users which server is hosting the lot /// /// It is then up to the client and server to connect to each other. This is a bit crappy because it means you /// must open a connection to the lot server before you can find out if there is room for you to join. But this removes /// a lot of complexity. /// /// In the future we may move the person claiming logic to city if it causes problems. One problem I can see is there is a possible /// race condition in which the lot could fill up before the owner gets in. The lot would then automatically shut down /// /// </summary> /// <param name="lotId"></param> /// <param name="avatarId">The id of the avatar opening this lot. If 0, we're opening for a scheduled cleanup. (lot start fresh)</param> /// <param name="openIfClosed"></param> /// <returns></returns> Task <TryFindLotResult> TryFind(uint lotId, uint avatarId, bool openIfClosed, ISecurityContext security) { bool jobLot = false; var originalId = lotId; if (lotId > 0x200 && lotId < 0x300) { //special: join available job lot instance var result = Matchmaker.TryGetJobLot(lotId, avatarId); lotId = result.Item1 ?? 0; lotId |= 0x40000000; originalId = result.Item2; jobLot = true; if (lotId == 0) { return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NO_CAPACITY })); } } else if (lotId > 0x200 && lotId < 0x10000) { //job lot range if (!Matchmaker.TryJoinExisting(lotId, avatarId)) { return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NO_ADMIT })); } lotId |= 0x40000000; jobLot = true; } var allocation = Get(lotId); lock (allocation) { switch (allocation.State) { case LotAllocationState.NOT_ALLOCATED: //We need to pick a server to run this lot if (!openIfClosed) { //Sorry, cant do this Remove(lotId); return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NOT_OPEN })); } if (!jobLot) { DbLot lot = null; using (var db = DAFactory.Get) { //Convert the lot location into a lot db id lot = db.Lots.GetByLocation(Context.ShardId, lotId); if (lot == null) { Remove(lotId); return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NO_SUCH_LOT })); } if (avatarId != 0) { var roomies = db.Roommates.GetLotRoommates(lot.lot_id); var modState = db.Avatars.GetModerationLevel(avatarId); var avatars = new List <uint>(); foreach (var roomie in roomies) { if (roomie.is_pending == 0) { avatars.Add(roomie.avatar_id); } } try { if (lot.admit_mode < 4 && modState == 0) { security.DemandAvatars(avatars, AvatarPermissions.WRITE); } } catch (Exception ex) { Remove(lotId); return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NOT_PERMITTED_TO_OPEN })); } } } if (!allocation.TryClaim(lot)) { Remove(lotId); return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.CLAIM_FAILED })); } allocation.SetLot(lot, 0, (avatarId == 0) ? ClaimAction.LOT_CLEANUP : ClaimAction.LOT_HOST); } else { allocation.SetLot(new DbLot() { lot_id = (int)lotId }, originalId, (avatarId == 0)? ClaimAction.LOT_CLEANUP : ClaimAction.LOT_HOST); } var pick = PickingEngine.PickServer(); if (pick.Success == false) { //Release claim allocation.TryUnclaim(); Remove(lotId); return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NO_CAPACITY })); } else { return(allocation.BeginPick(pick).ContinueWith(x => { if (x.IsFaulted || x.IsCanceled || x.Result.Accepted == false) { Remove(lotId); return new TryFindLotResult { Status = FindLotResponseStatus.NO_CAPACITY }; } return new TryFindLotResult { Status = FindLotResponseStatus.FOUND, Server = allocation.Server, LotDbId = allocation.LotDbId, LotId = lotId, }; })); } break; case LotAllocationState.ALLOCATING: break; case LotAllocationState.ALLOCATED: if (!jobLot && avatarId != 0) { //check admit type (might be expensive?) using (var db = DAFactory.Get) { var lot = db.Lots.GetByLocation(Context.ShardId, lotId); if (lot != null) { if (lot.admit_mode > 0 && lot.admit_mode < 4) { //special admit mode var roomies = db.Roommates.GetLotRoommates(lot.lot_id); var modState = db.Avatars.GetModerationLevel(avatarId); var avatars = new List <uint>(); foreach (var roomie in roomies) { avatars.Add(roomie.avatar_id); } try { if (modState == 0) { security.DemandAvatars(avatars, AvatarPermissions.WRITE); } } catch (Exception ex) { //if we're not a roommate, check admit rules if ((lot.admit_mode == 1 && !db.LotAdmit.GetLotAdmitDeny(lot.lot_id, 0).Contains(avatarId)) || //admit list (lot.admit_mode == 2 && db.LotAdmit.GetLotAdmitDeny(lot.lot_id, 1).Contains(avatarId)) || //ban list (lot.admit_mode == 3)) //ban all { return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.NO_ADMIT })); } } } } } } return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.FOUND, Server = allocation.Server, LotDbId = allocation.LotDbId, LotId = lotId })); //Should never get here.. case LotAllocationState.FAILED: Remove(lotId); return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.UNKNOWN_ERROR })); } return(Immediate(new TryFindLotResult { Status = FindLotResponseStatus.UNKNOWN_ERROR })); } }
public override void DemandMutation(object entity, MutationType type, string path, object value, ISecurityContext context) { var lot = entity as Lot; if (lot.DbId == 0) { throw new SecurityException("Unclaimed lots cannot be mutated"); } var roomies = lot.Lot_RoommateVec; switch (path) { //Owner only case "Lot_Description": context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); var desc = value as string; if (desc != null && desc.Length > 500) { throw new Exception("Description too long!"); } break; case "Lot_Name": context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); if (!GlobalRealestate.ValidateLotName((string)value)) { throw new Exception("Invalid lot name"); } //Lot_Name is a special case, it has to be unique so we have to hit the db in the security check //for this mutation. TryChangeLotName(lot, (string)value); break; case "Lot_Category": context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); if (lot.Lot_IsOnline) { throw new SecurityException("Lot must be offline to change category!"); } //7 days if (((Epoch.Now - lot.Lot_LastCatChange) / (60 * 60)) < 168) { throw new SecurityException("You must wait 7 days to change your lot category again"); } break; case "Lot_SkillGamemode": context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); var svalue = (uint)value; uint minSkill; if (!SkillGameplayCategory.TryGetValue((LotCategory)lot.Lot_Category, out minSkill)) { minSkill = 0; } if (Math.Min(2, Math.Max(minSkill, svalue)) != svalue) { throw new SecurityException("Invalid gamemode for this category."); } if (lot.Lot_IsOnline) { throw new SecurityException("Lot must be offline to change skill mode!"); } break; //roommate only case "Lot_Thumbnail": context.DemandAvatars(roomies, AvatarPermissions.WRITE); //TODO: needs to be generic data, png, size 288x288, less than 1MB break; case "Lot_IsOnline": case "Lot_NumOccupants": case "Lot_RoommateVec": case "Lot_SpotLightText": context.DemandInternalSystem(); break; case "Lot_LotAdmitInfo.LotAdmitInfo_AdmitList": case "Lot_LotAdmitInfo.LotAdmitInfo_BanList": context.DemandAvatars(roomies, AvatarPermissions.WRITE); int atype = (path == "Lot_LotAdmitInfo.LotAdmitInfo_AdmitList") ? 0 : 1; using (var db = DAFactory.Get()) { //need to check db constraints switch (type) { case MutationType.ARRAY_REMOVE_ITEM: //Remove bookmark at index value var removedAva = (uint)value; db.LotAdmit.Delete(new DbLotAdmit { lot_id = (int)lot.DbId, avatar_id = removedAva, admit_type = (byte)atype }); break; case MutationType.ARRAY_SET_ITEM: //Add a new bookmark var newAva = (uint)value; db.LotAdmit.Create(new DbLotAdmit { lot_id = (int)lot.DbId, avatar_id = newAva, admit_type = (byte)atype }); break; } } break; case "Lot_LotAdmitInfo.LotAdmitInfo_AdmitMode": context.DemandAvatars(roomies, AvatarPermissions.WRITE); //can only set valid values var mode = (byte)value; if (mode < 0 || mode > 3) { throw new Exception("Invalid admit mode!"); } break; default: throw new SecurityException("Field: " + path + " may not be mutated by users"); } }