public void Handle(IVoltronSession session, FindAvatarRequest packet) { if (session.IsAnonymous) { return; } using (var da = DAFactory.Get()) { var privacy = da.Avatars.GetPrivacyMode(packet.AvatarId); if (privacy > 0) { session.Write(new FindAvatarResponse { AvatarId = packet.AvatarId, LotId = 0, Status = FindAvatarResponseStatus.PRIVACY_ENABLED }); return; } //TODO: get ignore status var claim = da.AvatarClaims.GetByAvatarID(packet.AvatarId); //maybe check shard id against avatar in future. The client should do this anyways, and the server providing this functionality to everyone isnt a disaster. var location = claim?.location ?? 0; session.Write(new FindAvatarResponse { AvatarId = packet.AvatarId, LotId = location, Status = (location == 0)?FindAvatarResponseStatus.NOT_ON_LOT:FindAvatarResponseStatus.FOUND }); } }
public void Handle(IVoltronSession session, FindPlayerPDU packet) { session.Write(new FindPlayerResponsePDU { StatusCode = 0x00, ReasonText = "uh" }); }
public async void Handle(IVoltronSession session, AvatarRetireRequest packet) { if (session.IsAnonymous) //CAS users can't do this. { return; } try { using (var da = DA.Get) { var avatar = da.Avatars.Get(session.AvatarId); if (avatar == null) { return; } if (avatar.date > Epoch.Now - (60 * 60 * 24 * 7) && !da.Users.GetById(session.UserId).is_admin) { session.Write(new Protocol.Voltron.Packets.AnnouncementMsgPDU() { SenderID = "??" + "System", Message = "\r\n" + "You cannot delete a sim younger than a week old!", Subject = "Error" }); LOG.Info("Avatar " + avatar.name + " under account " + avatar.user_id + " attempted to delete a less than week old account."); session.Close(); return; } LOG.Info("Deleting avatar " + avatar.name + " under account " + avatar.user_id + "."); var lots = da.Roommates.GetAvatarsLots(session.AvatarId); foreach (var roomie in lots) { var lot = da.Lots.Get(roomie.lot_id); if (lot == null) { continue; } var kickResult = await Kernel.Get <ChangeRoommateHandler>().TryKick(lot.location, session.AvatarId, session.AvatarId); //if something goes wrong here, just return. if (kickResult != Protocol.Electron.Model.ChangeRoommateResponseStatus.SELFKICK_SUCCESS) { return; } } da.Avatars.Delete(session.AvatarId); } //now all our objects have null owner. objects with null owner will be cleaned out by the purge task (this needs to hit nfs & db). session.Close(); } catch { } }
public void Handle(IVoltronSession session, SetIgnoreListPDU packet) { session.Write(new SetIgnoreListResponsePDU { StatusCode = 0, ReasonText = "OK", MaxNumberOfIgnored = 50 }); }
public void UserJoined(IVoltronSession session) { if (TuningCache == null) { UpdateTuningCache(); } session.Write(new GlobalTuningUpdate() { Tuning = TuningCache, ObjectUpgrades = ObjectUpgradeData }); }
public void Send(uint avatarID, params object[] messages) { lock (_Visitors) { IVoltronSession visitor = null; if (_Visitors.TryGetValue(avatarID, out visitor)) { visitor.Write(messages); } } }
public void UserJoined(IVoltronSession session) { if (TuningCache == null) { UpdateTuningCache(); } session.Write(new GlobalTuningUpdate() { Tuning = TuningCache }); }
public void WriteFail(IVoltronSession session, InstantMessage message, InstantMessageFailureReason fail) { session.Write(new InstantMessage { FromType = FSO.Common.Enum.UserReferenceType.AVATAR, From = message.To, Type = InstantMessageType.FAILURE_ACK, Message = "", AckID = message.AckID, Reason = fail }); }
public bool TryJoin(IVoltronSession session) { if (Container.IsAvatarOnLot(session.AvatarId)) { session.Write(new FSOVMProtocolMessage(true, "11", "12")); return(false); //already on the lot. } lock (_Visitors) { if (ShuttingDown || (_Visitors.Count >= ((Context.HighMax)?256:24))) { if (ShuttingDown) { return(false); //cannot join } //check if this user has special permissions. should only happen when a lot is full //let them in anyways if they do using (var da = DAFactory.Get()) { var avatar = da.Avatars.Get(session.AvatarId); if (avatar.moderation_level == 0 && !Context.JobLot) { if (da.Roommates.Get(session.AvatarId, Context.DbId) == null) { session.Write(new FSOVMProtocolMessage(true, "15", "16")); return(false); //not a roommate } } } } session.SetAttribute("currentLot", ((Context.Id & 0x40000000) > 0)?(int)Context.Id:Context.DbId); _Visitors.Add(session.AvatarId, session); SyncNumVisitors(); InBackground(() => Container.AvatarJoin(session)); return(true); } }
private void HandleNetMessage(IVoltronSession session, cTSONetMessageStandard msg, DBRequestWrapperPDU packet) { if (!msg.DatabaseType.HasValue) { return; } var requestType = DBRequestTypeUtils.FromRequestID(msg.DatabaseType.Value); object response = null; switch (requestType) { case DBRequestType.LoadAvatarByID: response = HandleLoadAvatarById(session, msg); break; case DBRequestType.SearchExactMatch: response = HandleSearchExact(session, msg); break; case DBRequestType.Search: response = HandleSearchWildcard(session, msg); break; case DBRequestType.GetTopResultSetByID: response = HandleGetTop100(session, msg); break; } if (response != null) { session.Write(new DBRequestWrapperPDU { SendingAvatarID = packet.SendingAvatarID, Badge = packet.Badge, IsAlertable = packet.IsAlertable, Sender = packet.Sender, Body = response }); } }
/// <summary> /// The user is asking for some in RAM data /// </summary> /// <param name="session"></param> /// <param name="packet"></param> public async void Handle(IVoltronSession session, DataServiceWrapperPDU packet) { try //data service throws exceptions (SecurityException, etc) when invalid requests are made. These should not crash the server... { if (packet.Body is cTSONetMessageStandard) { var msg = (cTSONetMessageStandard)packet.Body; if (msg.ComplexParameter is cTSOTopicUpdateMessage) { var update = msg.ComplexParameter as cTSOTopicUpdateMessage; DataService.ApplyUpdate(update, session); return; } var type = MaskedStructUtils.FromID(packet.RequestTypeID); if (!msg.Parameter.HasValue) { return; } //if (type == MaskedStruct.MapView_NearZoom_Lot_Thumbnail || type == MaskedStruct.Thumbnail_Lot || type == MaskedStruct.MapView_NearZoom_Lot) { } if (type != MaskedStruct.MyAvatar && type != MaskedStruct.SimPage_Main && type != MaskedStruct.MapView_RollOverInfo_Lot_Price && type != MaskedStruct.MapView_RollOverInfo_Lot && type != MaskedStruct.Unknown && type != MaskedStruct.SimPage_DescriptionPanel && type != MaskedStruct.PropertyPage_LotInfo && type != MaskedStruct.Messaging_Message_Avatar && type != MaskedStruct.Messaging_Icon_Avatar && type != MaskedStruct.MapView_NearZoom_Lot_Thumbnail && type != MaskedStruct.Thumbnail_Lot && type != MaskedStruct.CurrentCity && type != MaskedStruct.MapView_NearZoom_Lot && type != MaskedStruct.Thumbnail_Avatar && type != MaskedStruct.SimPage_MyLot && type != MaskedStruct.SimPage_JobsPanel && type != MaskedStruct.FriendshipWeb_Avatar && type != MaskedStruct.SimPage_SkillsPanel && type != MaskedStruct.AdmitInfo_Lot) { //Currently broken for some reason return; } //Lookup the entity, then process the request and send the response var task = DataService.Get(type, msg.Parameter.Value); if (task != null) { var entity = await task; var serialized = DataService.SerializeUpdate(type, entity, msg.Parameter.Value); for (int i = 0; i < serialized.Count; i++) { object serial = serialized[i]; session.Write(new DataServiceWrapperPDU() { SendingAvatarID = packet.SendingAvatarID, RequestTypeID = packet.RequestTypeID, Body = serial }); } } } else if (packet.Body is cTSOTopicUpdateMessage) { //Client wants to update a value in the data service var update = packet.Body as cTSOTopicUpdateMessage; DataService.ApplyUpdate(update, session); List <uint> resultDotPath = new List <uint>(); foreach (var item in update.DotPath) { var ires = item; if (ires == 0x1095C1E1) { ires = 0x7EA285CD; //rewrite: filter id -> returns -> result list } resultDotPath.Add(ires); if (ires == packet.RequestTypeID) { break; } } var result = await DataService.SerializePath(resultDotPath.ToArray()); if (result != null) { session.Write(new DataServiceWrapperPDU() { SendingAvatarID = packet.SendingAvatarID, RequestTypeID = packet.RequestTypeID, Body = result }); } /*var task = DataService.Get(update.DotPath[0], update.DotPath[1]); * if(task != null) * { * var entity = await task; * * var serialized = DataService.SerializeUpdate(type, entity, msg.Parameter.Value); * }*/ /**/ } } catch (Exception e) { //SerializePath throws generic exceptions. //plus we don't want weird special cases crashing the whole server. LOG.Error(e, "Voltron DataService request failed!"); } }
/// <summary> /// Register a new avatar /// </summary> /// <param name="session"></param> /// <param name="packet"></param> public void Handle(IVoltronSession session, RSGZWrapperPDU packet) { PurchasableOutfit head = null; PurchasableOutfit body = null; switch (packet.Gender) { case Protocol.Voltron.Model.Gender.FEMALE: head = ValidFemaleOutfits[packet.HeadOutfitId]; body = ValidFemaleOutfits[packet.BodyOutfitId]; break; case Protocol.Voltron.Model.Gender.MALE: head = ValidMaleOutfits[packet.HeadOutfitId]; body = ValidMaleOutfits[packet.BodyOutfitId]; break; } if (head == null) { session.Write(new CreateASimResponse { Status = CreateASimStatus.FAILED, Reason = CreateASimFailureReason.HEAD_VALIDATION_ERROR }); return; } if (body == null) { session.Write(new CreateASimResponse { Status = CreateASimStatus.FAILED, Reason = CreateASimFailureReason.BODY_VALIDATION_ERROR }); return; } if (!NAME_VALIDATION.IsMatch(packet.Name)) { session.Write(new CreateASimResponse { Status = CreateASimStatus.FAILED, Reason = CreateASimFailureReason.NAME_VALIDATION_ERROR }); return; } if (!DESC_VALIDATION.IsMatch(packet.Description)) { session.Write(new CreateASimResponse { Status = CreateASimStatus.FAILED, Reason = CreateASimFailureReason.DESC_VALIDATION_ERROR }); return; } uint newId = 0; using (var db = DAFactory.Get) { var newAvatar = new DbAvatar { shard_id = Context.ShardId, name = packet.Name, description = packet.Description, date = Epoch.Now, head = head.OutfitID, body = body.OutfitID, skin_tone = (byte)packet.SkinTone, gender = packet.Gender == Protocol.Voltron.Model.Gender.FEMALE ? DbAvatarGender.female : DbAvatarGender.male, user_id = session.UserId, budget = 0 }; if (packet.Gender == Protocol.Voltron.Model.Gender.MALE) { newAvatar.body_swimwear = 0x5470000000D; newAvatar.body_sleepwear = 0x5440000000D; } else { newAvatar.body_swimwear = 0x620000000D; newAvatar.body_sleepwear = 0x5150000000D; } var user = db.Users.GetById(session.UserId); if ((user?.is_moderator) ?? false) { newAvatar.moderation_level = 1; } try { newId = db.Avatars.Create(newAvatar); } catch (Exception e) { //unique name error or avatar limit exceeded. //todo: special error for avatar limit? exception is thrown from BEFORE INSERT trigger. session.Write(new CreateASimResponse { Status = CreateASimStatus.FAILED, Reason = CreateASimFailureReason.NAME_TAKEN }); return; } //create clothes try { db.Outfits.Create(new Database.DA.Outfits.DbOutfit { avatar_owner = newId, asset_id = newAvatar.body, purchase_price = 0, sale_price = 0, outfit_type = (byte)VMPersonSuits.DefaultDaywear, outfit_source = Database.DA.Outfits.DbOutfitSource.cas }); db.Outfits.Create(new Database.DA.Outfits.DbOutfit { avatar_owner = newId, asset_id = newAvatar.body_sleepwear, purchase_price = 0, sale_price = 0, outfit_type = (byte)VMPersonSuits.DefaultSleepwear, outfit_source = Database.DA.Outfits.DbOutfitSource.cas }); db.Outfits.Create(new Database.DA.Outfits.DbOutfit { avatar_owner = newId, asset_id = newAvatar.body_swimwear, purchase_price = 0, sale_price = 0, outfit_type = (byte)VMPersonSuits.DefaultSwimwear, outfit_source = Database.DA.Outfits.DbOutfitSource.cas }); } catch (Exception e) { session.Write(new CreateASimResponse { Status = CreateASimStatus.FAILED, Reason = CreateASimFailureReason.NONE }); return; } } ((VoltronSession)session).AvatarId = newId; session.Write(new CreateASimResponse { Status = CreateASimStatus.SUCCESS, NewAvatarId = newId }); session.Write(new TransmitCreateAvatarNotificationPDU { }); }
public async void Handle(IVoltronSession session, NhoodRequest packet) { if (session.IsAnonymous) //CAS users can't do this. { return; } var config = Context.Config.Neighborhoods; var moveTime = config.Election_Move_Penalty * 24 * 60 * 60; //24 * 60 * 60 * 30; //must live in an nhood 30 days before participating in an election var rateMoveTime = config.Rating_Move_Penalty * 24 * 60 * 60; //24 * 60 * 60 * 30; //must live in an nhood 30 days before participating in an election var mail = Kernel.Get <MailHandler>(); try { using (var da = DA.Get()) { var myAva = da.Avatars.Get(session.AvatarId); if (myAva == null) { Code(NhoodResponseCode.UNKNOWN_ERROR); } if (packet.Type >= NhoodRequestType.DELETE_RATE) { if (myAva.moderation_level == 0) { session.Write(Code(NhoodResponseCode.NOT_MODERATOR)); return; } } //are we nhood gameplay banned? var ban = da.Neighborhoods.GetNhoodBan(myAva.user_id); if (ban != null) { session.Write(new NhoodResponse { Code = NhoodResponseCode.NHOOD_GAMEPLAY_BAN, Message = ban.ban_reason, BanEndDate = ban.end_date }); return; } //common info used by most requests var myLotID = da.Roommates.GetAvatarsLots(session.AvatarId).FirstOrDefault(); var myLot = (myLotID == null) ? null : da.Lots.Get(myLotID.lot_id); var freeVote = da.Elections.GetFreeVote(session.AvatarId); var freeNhood = (int)(myLot?.neighborhood_id ?? 0); var voteValue = (freeVote == null) ? config.Vote_Normal_Value : config.Vote_Free_Value; if (freeVote != null) { freeNhood = freeVote.neighborhood_id; } switch (packet.Type) { //user requests case NhoodRequestType.CAN_RATE: case NhoodRequestType.RATE: { if (packet.Value > 10 || packet.Value < 0 || packet.Message == null || packet.Message.Length > 200) { session.Write(Code(NhoodResponseCode.INVALID_RATING)); return; } if (myLot == null || myLot.neighborhood_id != packet.TargetNHood) { session.Write(Code(NhoodResponseCode.NOT_IN_NHOOD)); return; } if (session.AvatarId == packet.TargetAvatar) { session.Write(Code(NhoodResponseCode.CANT_RATE_AVATAR)); //you can't rate yourself... return; } if (Epoch.Now - myAva.move_date < rateMoveTime) { session.Write(Code(NhoodResponseCode.YOU_MOVED_RECENTLY)); return; } //verify the target avatar is the current mayor var rateNhood = da.Neighborhoods.Get(packet.TargetNHood); //user check: ratings are technically towards individual avatars, but are shared between the same user. var avaFrom = da.Avatars.Get(session.AvatarId); var avaTo = da.Avatars.Get(packet.TargetAvatar); if (rateNhood == null || avaFrom == null || avaTo == null) { session.Write(Code(NhoodResponseCode.UNKNOWN_ERROR)); //shouldn't happen, but just in case return; } if (rateNhood.mayor_id != packet.TargetAvatar) { session.Write(Code(NhoodResponseCode.NOT_YOUR_MAYOR)); //you can't rate yourself... return; } if (packet.Type == NhoodRequestType.RATE) { //insert or replace rating var id = da.Elections.SetRating(new DbMayorRating() { from_avatar_id = avaFrom.avatar_id, to_avatar_id = avaTo.avatar_id, from_user_id = avaFrom.user_id, to_user_id = avaTo.user_id, comment = packet.Message, rating = packet.Value, neighborhood = packet.TargetNHood, date = Epoch.Now, anonymous = 1, }); var ds = Kernel.Get <IDataService>(); if (id != 0) { ds.Invalidate <MayorRating>(id); //update this rating in data service } ds.Invalidate <Avatar>(packet.TargetAvatar); } session.Write(Code(NhoodResponseCode.SUCCESS)); return; } case NhoodRequestType.CAN_NOMINATE: case NhoodRequestType.NOMINATE: case NhoodRequestType.CAN_VOTE: case NhoodRequestType.VOTE: { if (freeVote != null) { packet.TargetNHood = (uint)freeNhood; } if (myLot == null || freeNhood != packet.TargetNHood) { session.Write(Code(NhoodResponseCode.NOT_IN_NHOOD)); return; } var now = Epoch.Now; if (now - myAva.move_date < moveTime) { session.Write(Code(NhoodResponseCode.YOU_MOVED_RECENTLY)); return; } //check if voting cycle in correct state var nhood = da.Neighborhoods.Get(packet.TargetNHood); if (nhood == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var cycle = (nhood.election_cycle_id == null) ? null : da.Elections.GetCycle(nhood.election_cycle_id.Value); if (cycle == null) { session.Write(Code(NhoodResponseCode.ELECTION_OVER)); return; } var nominations = packet.Type == NhoodRequestType.CAN_NOMINATE || packet.Type == NhoodRequestType.NOMINATE; if ((nominations && cycle.current_state != DbElectionCycleState.nomination) || (!nominations && cycle.current_state != DbElectionCycleState.election)) { session.Write(Code(NhoodResponseCode.BAD_STATE)); return; } DbAvatar targetAva = null; if (packet.Type != NhoodRequestType.CAN_NOMINATE && packet.Type != NhoodRequestType.CAN_VOTE) { //when we are actually nominating or voting, we have a target avatar in mind. targetAva = da.Avatars.Get(packet.TargetAvatar); if (targetAva == null) { session.Write(Code(NhoodResponseCode.INVALID_AVATAR)); return; } var targLotID = da.Roommates.GetAvatarsLots(packet.TargetAvatar).FirstOrDefault(); var targLot = (targLotID == null) ? null : da.Lots.Get(targLotID.lot_id); if (targLot == null || targLot.neighborhood_id != packet.TargetNHood) { session.Write(Code(NhoodResponseCode.CANDIDATE_NOT_IN_NHOOD)); return; } var targBan = da.Neighborhoods.GetNhoodBan(targetAva.user_id); if (targBan != null) { session.Write(Code(NhoodResponseCode.CANDIDATE_NHOOD_GAMEPLAY_BAN)); return; } if (now - targetAva.move_date < moveTime) { session.Write(Code(NhoodResponseCode.CANDIDATE_MOVED_RECENTLY)); return; } } //ok, we're all good. if (!nominations) { //have we already voted? //note: this should be double checked with a BEFORE INSERT on the table. //just in case there's some kind of race condition due to this request being duplicated. //this check is therefore just to get a better voting error message. DbElectionVote existing = da.Elections.GetMyVote(session.AvatarId, cycle.cycle_id, DbElectionVoteType.vote); if (existing != null) { if (existing.from_avatar_id != session.AvatarId) { session.Write(Code(NhoodResponseCode.ALREADY_VOTED_SAME_IP)); //couldn't vote due to relation } else { session.Write(Code(NhoodResponseCode.ALREADY_VOTED)); } } if (packet.Type == NhoodRequestType.CAN_VOTE) { //we are allowed to vote //send back the candidate list var sims = da.Elections.GetCandidates(cycle.cycle_id); var result = new List <NhoodCandidate>(); foreach (var x in sims) { if (x.state != DbCandidateState.running) { continue; } var ava = (await DataService.Get <Avatar>(x.candidate_avatar_id)); if (ava == null) { continue; } var win = da.Elections.FindLastWin(x.candidate_avatar_id); result.Add(new NhoodCandidate() { ID = x.candidate_avatar_id, Name = ava.Avatar_Name, Rating = ava.Avatar_MayorRatingHundredth, Message = x.comment, LastNhoodName = win?.nhood_name ?? "", LastNhoodID = win?.nhood_id ?? 0, TermNumber = (uint)((nhood.mayor_id == x.candidate_avatar_id) ? GetTermsSince(nhood.mayor_elected_date) : 0) }); } session.Write(new NhoodCandidateList() { NominationMode = false, Candidates = result.ToList() }); session.Write(Code(NhoodResponseCode.SUCCESS)); return; } //extra checks. has the target user been nominated? if (!da.Elections.GetCandidates(cycle.cycle_id, DbCandidateState.running).Any(x => x.candidate_avatar_id == packet.TargetAvatar)) { session.Write(Code(NhoodResponseCode.CANDIDATE_NOT_NOMINATED)); return; } //put our vote through! democracy is great. var success = da.Elections.CreateVote(new DbElectionVote() { date = Epoch.Now, election_cycle_id = cycle.cycle_id, from_avatar_id = session.AvatarId, target_avatar_id = packet.TargetAvatar, type = DbElectionVoteType.vote, value = voteValue }); if (!success) { session.Write(Code(NhoodResponseCode.ALREADY_VOTED)); return; } mail.SendSystemEmail("f116", (int)NeighMailStrings.VoteCountedSubject, (int)NeighMailStrings.VoteCounted, 1, MessageSpecialType.Normal, cycle.end_date, session.AvatarId, targetAva.name, nhood.name); session.Write(Code(NhoodResponseCode.SUCCESS)); } else { //have we already nominated? DbElectionVote existing = da.Elections.GetMyVote(session.AvatarId, cycle.cycle_id, DbElectionVoteType.nomination); if (existing != null) { if (existing.from_avatar_id != session.AvatarId) { session.Write(Code(NhoodResponseCode.ALREADY_VOTED_SAME_IP)); //couldn't nominate due to relation } else { session.Write(Code(NhoodResponseCode.ALREADY_VOTED)); } } if (packet.Type == NhoodRequestType.CAN_NOMINATE) { //we are allowed to nominate. //send back the list of sims in nhood var sims = da.Avatars.GetPossibleCandidatesNhood((uint)packet.TargetNHood); var result = sims.Select(x => new NhoodCandidate() { ID = x.avatar_id, Name = x.name, Rating = (x.rating == null) ? uint.MaxValue : (uint)((x.rating / 2) * 100) }); session.Write(new NhoodCandidateList() { NominationMode = true, Candidates = result.ToList() }); session.Write(Code(NhoodResponseCode.SUCCESS)); return; } //do the nomination. var success = da.Elections.CreateVote(new DbElectionVote() { date = Epoch.Now, election_cycle_id = cycle.cycle_id, from_avatar_id = session.AvatarId, target_avatar_id = packet.TargetAvatar, type = DbElectionVoteType.nomination, value = voteValue }); if (!success) { session.Write(Code(NhoodResponseCode.ALREADY_VOTED)); return; } //if >= 3 nominations, allow the player to run for election. var noms = da.Elections.GetCycleVotesForAvatar(packet.TargetAvatar, cycle.cycle_id, DbElectionVoteType.nomination); if (noms.Count() >= config.Min_Nominations) { var created = da.Elections.CreateCandidate(new DbElectionCandidate() { candidate_avatar_id = packet.TargetAvatar, election_cycle_id = cycle.cycle_id, comment = packet.Message, state = DbCandidateState.informed }); //only send the mail if they have not accepted. if (created) { mail.SendSystemEmail("f116", (int)NeighMailStrings.NominationQuerySubject, (int)NeighMailStrings.NominationQuery, 1, MessageSpecialType.AcceptNomination, cycle.end_date, packet.TargetAvatar, nhood.name, cycle.end_date.ToString()); } } mail.SendSystemEmail("f116", (int)NeighMailStrings.NominationCountedSubject, (int)NeighMailStrings.NominationCounted, 1, MessageSpecialType.Normal, cycle.end_date, session.AvatarId, targetAva.name, nhood.name); session.Write(Code(NhoodResponseCode.SUCCESS)); } break; } case NhoodRequestType.NOMINATION_RUN: case NhoodRequestType.CAN_RUN: { //if a user has been nominated 3 or more times, they will be asked if they would like to run for mayor by email. //the email will contain a button that lets the user accept the nomination. //this doesn't immediately get them running for mayor, it means that they are a valid selection from //the top nominated candidates when the nominations are finalized. //first check if we have the three required nominations if (myLot == null || myLot.neighborhood_id != packet.TargetNHood) { session.Write(Code(NhoodResponseCode.NOT_IN_NHOOD)); return; } var nhood = da.Neighborhoods.Get(packet.TargetNHood); if (nhood == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var cycle = (nhood.election_cycle_id == null) ? null : da.Elections.GetCycle(nhood.election_cycle_id.Value); if (cycle == null) { session.Write(Code(NhoodResponseCode.ELECTION_OVER)); return; } if (cycle.current_state != DbElectionCycleState.nomination) { session.Write(Code(NhoodResponseCode.BAD_STATE)); return; } //have we been nominated the minimum number of times? (3) var noms = da.Elections.GetCycleVotesForAvatar(session.AvatarId, cycle.cycle_id, DbElectionVoteType.nomination); if (noms.Count < config.Min_Nominations) { session.Write(Code(NhoodResponseCode.NOBODY_NOMINATED_YOU_IDIOT)); return; } if (packet.Type == NhoodRequestType.CAN_RUN) { if (da.Elections.GetCandidate(session.AvatarId, cycle.cycle_id, DbCandidateState.informed) == null) { session.Write(Code(NhoodResponseCode.ALREADY_RUNNING)); } else { session.Write(Code(NhoodResponseCode.SUCCESS)); } } else { var success = da.Elections.SetCandidateState(new DbElectionCandidate() { candidate_avatar_id = session.AvatarId, election_cycle_id = cycle.cycle_id, comment = packet.Message, state = DbCandidateState.running }); if (success) { mail.SendSystemEmail("f116", (int)NeighMailStrings.NominationAcceptedSubject, (int)NeighMailStrings.NominationAccepted, 1, MessageSpecialType.Normal, cycle.end_date, session.AvatarId, nhood.name, cycle.end_date.ToString()); session.Write(Code(NhoodResponseCode.SUCCESS)); } else { session.Write(Code(NhoodResponseCode.ALREADY_RUNNING)); } } return; } case NhoodRequestType.CAN_FREE_VOTE: case NhoodRequestType.FREE_VOTE: { //should live somewhere: if (myLot == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } //we shouldn't already be enrolled if (freeNhood != myLot.neighborhood_id) { session.Write(Code(NhoodResponseCode.ALREADY_ENROLLED_FOR_FREE_VOTE)); return; } //our nhood needs to be ineligible var ourNhood = da.Neighborhoods.Get(myLot.neighborhood_id); if (ourNhood == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var cycle = (ourNhood.election_cycle_id == null) ? null : da.Elections.GetCycle(ourNhood.election_cycle_id.Value); if (cycle != null && cycle.current_state != DbElectionCycleState.shutdown) { session.Write(Code(NhoodResponseCode.FREE_VOTE_ALREADY_ELIGIBLE)); return; } //get eligible nhoods var eligible = da.Elections.GetActiveCycles(Context.ShardId); if (eligible.Count == 0) { session.Write(Code(NhoodResponseCode.FREE_VOTE_ELECTION_OVER)); return; } //our last move needs to have been before the nhood cycle started var now = Epoch.Now; if (eligible.All(x => x.start_date < myAva.move_date)) { session.Write(Code(NhoodResponseCode.FREE_VOTE_MOVE_DATE)); return; } if (packet.Type == NhoodRequestType.CAN_FREE_VOTE) { //send the eligible nhoods var result = eligible.Select(x => new NhoodCandidate() { ID = (uint)x.nhood_id, Name = x.name, Rating = uint.MaxValue }); session.Write(new NhoodCandidateList() { NominationMode = true, Candidates = result.ToList() }); session.Write(Code(NhoodResponseCode.SUCCESS)); return; } //the target nhood needs to be eligible, and have an ongoing election var targ = da.Neighborhoods.Get(packet.TargetNHood); if (targ == null || targ.election_cycle_id == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var targCycle = da.Elections.GetCycle((uint)targ.election_cycle_id); if (targCycle == null || targCycle.current_state == DbElectionCycleState.shutdown) { session.Write(Code(NhoodResponseCode.NHOOD_NO_ELECTION)); return; } var fVote = new DbElectionFreeVote() { avatar_id = session.AvatarId, cycle_id = targCycle.cycle_id, neighborhood_id = targ.neighborhood_id, date = Epoch.Now, expire_date = targCycle.end_date }; if (!da.Elections.EnrollFreeVote(fVote)) { session.Write(Code(NhoodResponseCode.ALREADY_ENROLLED_FOR_FREE_VOTE)); return; } mail.SendSystemEmail("f116", (int)NeighMailStrings.FreeVoteConfirmationSubject, (int)NeighMailStrings.FreeVoteConfirmation, 1, MessageSpecialType.Normal, targCycle.end_date, session.AvatarId, targ.name); Nhoods.SendStateEmail(da, mail, targ, targCycle, session.AvatarId); session.Write(Code(NhoodResponseCode.SUCCESS)); return; } // ======= management ======= case NhoodRequestType.DELETE_RATE: var beforeDelete = da.Elections.GetRating(packet.Value); if (da.Elections.DeleteRating(packet.Value)) { var ds = Kernel.Get <IDataService>(); try { ds.Invalidate <Avatar>(beforeDelete.from_avatar_id); ds.Invalidate <MayorRating>(packet.Value); //update this rating in data service } catch (Exception) { } session.Write(new NhoodResponse() { Code = NhoodResponseCode.SUCCESS, Message = (beforeDelete?.from_avatar_id ?? 0).ToString() }); } else { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); } return; case NhoodRequestType.FORCE_MAYOR: //set the mayor. await Nhoods.SetMayor(da, packet.TargetAvatar, packet.TargetNHood); session.Write(Code(NhoodResponseCode.SUCCESS)); return; case NhoodRequestType.ADD_CANDIDATE: //check if voting cycle in correct state { var nhood = da.Neighborhoods.Get(packet.TargetNHood); if (nhood == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var cycle = (nhood.election_cycle_id == null) ? null : da.Elections.GetCycle(nhood.election_cycle_id.Value); if (cycle == null) { session.Write(Code(NhoodResponseCode.ELECTION_OVER)); return; } //while candidates DO have a use in nominations, adding them is largely pointless before the elections have begun //(because they will likely be removed when choosing the top 5 anyways) if (cycle.current_state != DbElectionCycleState.election) { session.Write(Code(NhoodResponseCode.BAD_STATE)); return; } } break; case NhoodRequestType.REMOVE_CANDIDATE: //get current cycle { var nhood = da.Neighborhoods.Get(packet.TargetNHood); if (nhood == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var cycle = (nhood.election_cycle_id == null) ? null : da.Elections.GetCycle(nhood.election_cycle_id.Value); if (cycle == null) { session.Write(Code(NhoodResponseCode.ELECTION_OVER)); return; } if (da.Elections.DeleteCandidate(cycle.cycle_id, packet.TargetAvatar)) { session.Write(Code(NhoodResponseCode.SUCCESS)); } else { session.Write(Code(NhoodResponseCode.INVALID_AVATAR)); } } break; case NhoodRequestType.TEST_ELECTION: //create an election cycle { var nhood = da.Neighborhoods.Get(packet.TargetNHood); if (nhood == null) { session.Write(Code(NhoodResponseCode.MISSING_ENTITY)); return; } var cycle = new DbElectionCycle() { current_state = DbElectionCycleState.nomination, election_type = DbElectionCycleType.election, start_date = Epoch.Now, end_date = packet.Value }; var cycleID = da.Elections.CreateCycle(cycle); if (cycleID == 0) { session.Write(Code(NhoodResponseCode.UNKNOWN_ERROR)); return; } cycle.cycle_id = cycleID; nhood.election_cycle_id = cycleID; da.Neighborhoods.UpdateCycle((uint)nhood.neighborhood_id, cycleID); await Nhoods.ChangeElectionState(da, nhood, cycle, (DbElectionCycleState)packet.TargetAvatar); session.Write(Code(NhoodResponseCode.SUCCESS)); return; } case NhoodRequestType.PRETEND_DATE: try { uint day = 60 * 60 * 24; Nhoods.SaveCheatOffset((((packet.Value - Epoch.Now) + day - 1) / day) * day); await Nhoods.TickNeighborhoods(Epoch.ToDate(packet.Value)); session.Write(Code(NhoodResponseCode.SUCCESS)); } catch (Exception e) { session.Write(new NhoodResponse() { Code = NhoodResponseCode.UNKNOWN_ERROR, Message = e.ToString() }); } return; case NhoodRequestType.NHOOD_GAMEPLAY_BAN: { var targetAva = da.Avatars.Get(packet.TargetAvatar); if (targetAva == null) { session.Write(Code(NhoodResponseCode.INVALID_AVATAR)); } var success = da.Neighborhoods.AddNhoodBan(new DbNhoodBan() { user_id = targetAva.user_id, ban_reason = packet.Message, end_date = packet.Value }); if (success) { mail.SendSystemEmail("f116", (int)NeighMailStrings.NeighGameplayBanSubject, (int)NeighMailStrings.NeighGameplayBan, 1, MessageSpecialType.Normal, packet.Value, packet.TargetAvatar, packet.Message, packet.Value.ToString()); } session.Write(Code(NhoodResponseCode.SUCCESS)); return; } default: session.Write(Code(NhoodResponseCode.UNKNOWN_ERROR)); return; } } } catch (Exception e) { LOG.Error(e.ToString()); session?.Write(Code(NhoodResponseCode.UNKNOWN_ERROR)); } }
public async void Handle(IVoltronSession session, PurchaseLotRequest packet) { if (session.IsAnonymous) //CAS users can't do this. { return; } var isPurchasable = Realestate.IsPurchasable(packet.LotLocation_X, packet.LotLocation_Y); if (!isPurchasable) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.LOT_NOT_PURCHASABLE }); return; } var packedLocation = MapCoordinates.Pack(packet.LotLocation_X, packet.LotLocation_Y); var price = Realestate.GetPurchasePrice(packet.LotLocation_X, packet.LotLocation_Y); int resultFunds; uint lotId = 0; using (var db = DA.Get()) { var ownedLot = db.Lots.GetByOwner(session.AvatarId); if (ownedLot != null) { //we own the lot we are roomie of. var roommates = db.Roommates.GetLotRoommates(ownedLot.lot_id); var ds = await DataService.Get <FSO.Common.DataService.Model.Lot>(ownedLot.location); if (ds.Lot_IsOnline) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NOT_OFFLINE_FOR_MOVE //TODO: race condition might make this possible? }); return; } if (roommates.Count > 1) { //cannot start fresh with roommates for now. packet.StartFresh = false; } var oldLoc = MapCoordinates.Unpack(ownedLot.location); var moveCost = price - Realestate.GetPurchasePrice(oldLoc.X, oldLoc.Y); moveCost += 2000; //flat rate for moving location var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, moveCost, 5); //expenses misc... maybe add specific for lot resultFunds = transactionResult.source_budget; if (!transactionResult.success) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.INSUFFICIENT_FUNDS, NewFunds = resultFunds }); return; } db.Lots.UpdateLocation(ownedLot.lot_id, packedLocation, packet.StartFresh); DataService.Invalidate <FSO.Common.DataService.Model.Lot>(ownedLot.location); //nullify old lot DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation); //update new lot } else { //we may still be roomie in a lot. If we are, we must be removed from that lot. var myLots = db.Roommates.GetAvatarsLots(session.AvatarId); if (myLots.Count > 0) { //we can't be in the lot when this happens. Make sure city owns avatar. bool canEvict = session.AvatarClaimId != 0; if (!canEvict) { var claim = db.AvatarClaims.Get(session.AvatarClaimId); if (claim.owner == Context.Config.Call_Sign) { db.Roommates.RemoveRoommate(session.AvatarId, myLots[0].lot_id); canEvict = true; } else { canEvict = false; } } if (!canEvict) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.IN_LOT_CANT_EVICT }); return; } } var name = packet.Name; if (!GlobalRealestate.ValidateLotName(name)) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NAME_VALIDATION_ERROR }); return; } var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, price, 5); //expenses misc... maybe add specific for lot resultFunds = transactionResult.source_budget; if (!transactionResult.success) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.INSUFFICIENT_FUNDS, NewFunds = resultFunds }); return; } try { lotId = db.Lots.Create(new DbLot { name = name, shard_id = Context.ShardId, location = packedLocation, owner_id = session.AvatarId, created_date = Epoch.Now, category_change_date = Epoch.Default, category = LotCategory.none, buildable_area = 1, description = "" }); DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation); } catch (Exception ex) { var returnMoney = db.Avatars.Transaction(uint.MaxValue, session.AvatarId, price, 5); //refund //Name taken if (ex.Message == "NAME") { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NAME_TAKEN, //TODO: this can also happen if the location was taken. (location is a UNIQUE row) NewFunds = returnMoney.dest_budget }); } else { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.UNKNOWN, //likely already roommate somewhere else, or we got race condition'd by another roomie request NewFunds = returnMoney.dest_budget }); } return; } } } //lot init happens on first join, as part of the loading process. If the lot somehow crashes before first save, it'll just be a blank slate again. //TODO: Broadcast to the world a new lot exists. i think we do this? //Update my sim's lot var avatar = await DataService.Get <Avatar>(session.AvatarId); if (avatar != null) { avatar.Avatar_LotGridXY = packedLocation; } session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.SUCCESS, NewLotId = lotId, NewFunds = resultFunds }); }
public async void Handle(IVoltronSession session, MailRequest message) { if (session.IsAnonymous) //CAS users can't do this. { return; } try { switch (message.Type) { case MailRequestType.POLL_INBOX: //when a user logs in they will send this request to recieve all messages after their last recieved message. using (var da = DA.Get()) { var msgs = da.Inbox.GetMessagesAfter(session.AvatarId, new DateTime(message.TimestampID)); session.Write(new MailResponse() { Type = MailResponseType.POLL_RESPONSE, Messages = msgs.Select(x => ToItem(x)).ToArray() }); return; } case MailRequestType.DELETE: //when a user deletes a message from their pc, it should also be deleted from the server. using (var da = DA.Get()) { da.Inbox.DeleteMessage((int)message.TimestampID, session.AvatarId); return; } case MailRequestType.SEND: using (var da = DA.Get()) { //admins get to change their message type, source, etc, and are never throttled. var modLevel = da.Avatars.GetModerationLevel(session.AvatarId); var them = await DataService.Get <FSO.Common.DataService.Model.Avatar>(message.Item.TargetID); var toName = them.Avatar_Name; if (modLevel == 0) { var bookmarks = them.Avatar_BookmarksVec; if (bookmarks.Any(x => x.Bookmark_Type == 5 && x.Bookmark_TargetID == session.AvatarId)) { session.Write(new MailResponse() { Type = MailResponseType.SEND_IGNORING_YOU, }); return; } var you = await DataService.Get <FSO.Common.DataService.Model.Avatar>(session.AvatarId); bookmarks = you.Avatar_BookmarksVec; if (bookmarks.Any(x => x.Bookmark_Type == 5 && x.Bookmark_TargetID == message.Item.TargetID)) { session.Write(new MailResponse() { Type = MailResponseType.SEND_IGNORING_THEM, }); return; } message.Item.Type = 0; message.Item.SenderID = session.AvatarId; message.Item.SenderName = you.Avatar_Name; } message.Item.ReplyID = null; //currently unused, but may be used in future to track conversations. var body = BBCodeParser.SanitizeBB(message.Item.Body); if (body.Length > 1500) { body = body.Substring(0, 1500); } message.Item.Body = body; if (SendEmail(message.Item, true)) { //give the sent message back to the sender for safe keeping. if (toName != null) { message.Item.SenderName = "To: " + toName; message.Item.SenderID = message.Item.TargetID; message.Item.ReadState = 1; } session.Write(new MailResponse() { Type = MailResponseType.SEND_SUCCESS, Messages = new MessageItem[] { message.Item } }); return; } else { session.Write(new MailResponse() { Type = MailResponseType.SEND_FAILED, Messages = new MessageItem[] { message.Item } }); return; } } } } catch { if (message.Type == MailRequestType.SEND) { session.Write(new MailResponse() { Type = MailResponseType.SEND_FAILED, Messages = new MessageItem[] { message.Item } }); } return; } }
public async void Handle(IVoltronSession session, PurchaseLotRequest packet) { if (session.IsAnonymous) //CAS users can't do this. { return; } var isPurchasable = Realestate.IsPurchasable(packet.LotLocation_X, packet.LotLocation_Y); if (!isPurchasable) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.LOT_NOT_PURCHASABLE }); return; } var packedLocation = MapCoordinates.Pack(packet.LotLocation_X, packet.LotLocation_Y); var price = Realestate.GetPurchasePrice(packet.LotLocation_X, packet.LotLocation_Y); if (packet.MayorMode) { price = 2000; } int resultFunds; uint lotId = 0; using (var db = DA.Get()) { if (db.Lots.GetByLocation(Context.ShardId, packedLocation) != null) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.LOT_TAKEN, }); return; } var targNhood = db.Neighborhoods.GetByLocation(packedLocation); var nhoodDS = await DataService.Get <Neighborhood>((uint)targNhood.neighborhood_id); DbLot ownedLot; if (packet.MayorMode) { // we want to move or place the town hall lot for the neighbourhood we're mayor of. // 1. find what neighbourhood we are mayor of. fail if none. // 2. verify the chosen location is in the correct neighborhood. fail if not // 3. get the town hall lot for our nhood. exists = move, doesn't = new var nhood = db.Neighborhoods.GetByMayor(session.AvatarId); if (nhood == null) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.TH_NOT_MAYOR }); return; } //verify the neighbourhood makes sense if (nhood.neighborhood_id != (targNhood?.neighborhood_id ?? 0)) { //we can't put this neighbourhood's town hall in another neighbourhood. session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.TH_INCORRECT_NHOOD }); return; } //find the town hall property if (nhood.town_hall_id == null) { ownedLot = null; } else { ownedLot = db.Lots.Get(nhood.town_hall_id.Value); } } else { if ((targNhood.flag & 1) > 0) { var me = db.Avatars.Get(session.AvatarId); if (me == null || me.moderation_level == 0) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NHOOD_RESERVED, }); return; } } ownedLot = db.Lots.GetByOwner(session.AvatarId); } if (ownedLot != null) { //we own the lot we are roomie of. var roommates = db.Roommates.GetLotRoommates(ownedLot.lot_id); var ds = await DataService.Get <FSO.Common.DataService.Model.Lot>(ownedLot.location); if (ds.Lot_IsOnline) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NOT_OFFLINE_FOR_MOVE //TODO: race condition might make this possible? }); return; } if (roommates.Count > 1) { //cannot start fresh with roommates for now. packet.StartFresh = false; } var oldLoc = MapCoordinates.Unpack(ownedLot.location); var moveCost = price - Realestate.GetPurchasePrice(oldLoc.X, oldLoc.Y); moveCost += 2000; //flat rate for moving location if (packet.MayorMode) { moveCost = 2000; } var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, moveCost, 5); //expenses misc... maybe add specific for lot resultFunds = transactionResult.source_budget; if (!transactionResult.success) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.INSUFFICIENT_FUNDS, NewFunds = resultFunds }); return; } if (!db.Lots.UpdateLocation(ownedLot.lot_id, packedLocation, packet.StartFresh)) { //needs to refund the player. var transactionResult2 = db.Avatars.Transaction(uint.MaxValue, session.AvatarId, moveCost, 5); session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.LOT_TAKEN, NewFunds = transactionResult2.source_budget }); return; } DataService.Invalidate <FSO.Common.DataService.Model.Lot>(ownedLot.location); //nullify old lot DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation); //update new lot if (packet.MayorMode) { if (nhoodDS != null) { nhoodDS.Neighborhood_TownHallXY = packedLocation; } } if (nhoodDS != null) { nhoodDS.Neighborhood_LotCount = (uint)db.Lots.GetLocationsInNhood(nhoodDS.Id).Count; nhoodDS.Neighborhood_AvatarCount = (uint)db.Avatars.GetLivingInNhood(nhoodDS.Id).Count; } var oldNhood = await DataService.Get <Neighborhood>(ownedLot.neighborhood_id); if (oldNhood != null) { oldNhood.Neighborhood_LotCount = (uint)db.Lots.GetLocationsInNhood(oldNhood.Id).Count; oldNhood.Neighborhood_AvatarCount = (uint)db.Avatars.GetLivingInNhood(oldNhood.Id).Count; } //update nhood move date for all roommates if (ownedLot.neighborhood_id != targNhood.neighborhood_id) { foreach (var roomie in roommates) { db.Avatars.UpdateMoveDate(roomie.avatar_id, Epoch.Now); } } } else { //we may still be roomie in a lot. If we are, we must be removed from that lot. if (!packet.MayorMode) { var myLots = db.Roommates.GetAvatarsLots(session.AvatarId); if (myLots.Count > 0) { if (myLots[0].permissions_level > 1) { //owner should not be able to move out of a lot implicitly session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.UNKNOWN }); return; } var lot = db.Lots.Get(myLots[0].lot_id); if (lot != null) { var kickResult = await Kernel.Get <ChangeRoommateHandler>().TryKick(lot.location, session.AvatarId, session.AvatarId); if (kickResult != Protocol.Electron.Model.ChangeRoommateResponseStatus.SELFKICK_SUCCESS) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.IN_LOT_CANT_EVICT }); return; } } /* * //we can't be in the lot when this happens. Make sure city owns avatar. * bool canEvict = session.AvatarClaimId != 0; * if (!canEvict) * { * var claim = db.AvatarClaims.Get(session.AvatarClaimId); * if (claim.owner == Context.Config.Call_Sign) * { * db.Roommates.RemoveRoommate(session.AvatarId, myLots[0].lot_id); * canEvict = true; * } * else canEvict = false; * } * * if (!canEvict) * { * session.Write(new PurchaseLotResponse() * { * Status = PurchaseLotStatus.FAILED, * Reason = PurchaseLotFailureReason.IN_LOT_CANT_EVICT * }); * return; * } */ } } var name = packet.Name; if (!GlobalRealestate.ValidateLotName(name)) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NAME_VALIDATION_ERROR }); return; } var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, price, 5); //expenses misc... maybe add specific for lot resultFunds = transactionResult.source_budget; if (!transactionResult.success) { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.INSUFFICIENT_FUNDS, NewFunds = resultFunds }); return; } try { lotId = db.Lots.Create(new DbLot { name = name, shard_id = Context.ShardId, location = packedLocation, owner_id = session.AvatarId, created_date = Epoch.Now, category_change_date = Epoch.Default, category = (packet.MayorMode) ? LotCategory.community : LotCategory.none, neighborhood_id = (uint)targNhood.neighborhood_id, buildable_area = 1, description = "" }); DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation); if (packet.MayorMode) { db.Neighborhoods.UpdateTownHall((uint)targNhood.neighborhood_id, lotId); if (nhoodDS != null) { nhoodDS.Neighborhood_TownHallXY = packedLocation; } } if (nhoodDS != null) { nhoodDS.Neighborhood_LotCount = (uint)db.Lots.GetLocationsInNhood(nhoodDS.Id).Count; nhoodDS.Neighborhood_AvatarCount = (uint)db.Avatars.GetLivingInNhood(nhoodDS.Id).Count; db.Avatars.UpdateMoveDate(session.AvatarId, Epoch.Now); } } catch (Exception ex) { var returnMoney = db.Avatars.Transaction(uint.MaxValue, session.AvatarId, price, 5); //refund //Name taken if (ex.Message == "NAME") { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.NAME_TAKEN, //TODO: this can also happen if the location was taken. (location is a UNIQUE row) NewFunds = returnMoney.dest_budget }); } else { session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.FAILED, Reason = PurchaseLotFailureReason.UNKNOWN, //likely already roommate somewhere else, or we got race condition'd by another roomie request NewFunds = returnMoney.dest_budget }); } return; } } } //lot init happens on first join, as part of the loading process. If the lot somehow crashes before first save, it'll just be a blank slate again. //Update my sim's lot var avatar = await DataService.Get <Avatar>(session.AvatarId); if (avatar != null) { avatar.Avatar_LotGridXY = packedLocation; } session.Write(new PurchaseLotResponse() { Status = PurchaseLotStatus.SUCCESS, NewLotId = lotId, NewFunds = resultFunds }); }
public async void Handle(IVoltronSession session, FindLotRequest packet) { if (session.IsAnonymous) //CAS users can't do this. { return; } try { //special modes at 0x200-0x1000 //0x200: join my job lot //0x201-0x2FF: create job lot of type/lotgroup (client CANNOT join on demand.) // 0x01-0x10: Robot Factory // 0x11-0x20: Restaurant // 0x21-0x30: DJ/Dancer //0x300+: instanced lots (client CAN join on demand, and appear in data service) //note: lotgroup is not the same as a grade. eg. restaurant grade 5+6 share a lot. (zero based grade) //nightclub 7-10 share a lot group. (two lots, one chosen randomly) if (packet.LotId >= 0x200 && packet.LotId < 0x300) { //join my job lot. //look up our avatar's current job and attempt to match them to a job lot with <max players, or a new one. using (var db = DAFactory.Get) { var job = db.Avatars.GetCurrentJobLevel(session.AvatarId); if (job == null) { session.Write(new FindLotResponse { Status = Protocol.Electron.Model.FindLotResponseStatus.UNKNOWN_ERROR, LotId = packet.LotId }); } //ok, choose the correct type/lotgroup combo var type = job.job_type; //if (type > 2) type--; //cook and waiter share job lot //if (type > 3) type--; //dj and dancer share job lot packet.LotId = (uint)(0x201 + (type - 1) * 0x10 + job.job_level); } } var find = await Lots.TryFindOrOpen(packet.LotId, session.AvatarId, session); //null reference exception possible here if (find.Status == Protocol.Electron.Model.FindLotResponseStatus.FOUND) { DbLotServerTicket ticket = null; using (var db = DAFactory.Get) { //I need a shard ticket so I can connect to the lot server and assume the correct avatar ticket = new DbLotServerTicket { ticket_id = Guid.NewGuid().ToString().Replace("-", ""), user_id = session.UserId, avatar_id = session.AvatarId, lot_owner = find.Server.CallSign, date = Epoch.Now, ip = session.IpAddress, lot_id = find.LotDbId, avatar_claim_id = session.AvatarClaimId, avatar_claim_owner = Context.Config.Call_Sign }; db.Lots.CreateLotServerTicket(ticket); } session.Write(new FindLotResponse { Status = find.Status, LotId = find.LotId, //can be modified by job matchmaker LotServerTicket = ticket.ticket_id, Address = find.Server.PublicHost, User = session.UserId.ToString() }); } else { session.Write(new FindLotResponse { Status = find.Status, LotId = packet.LotId }); } } catch (Exception e) { LOG.Error(e); } }
public async void Handle(IVoltronSession session, ChangeRoommateRequest packet) { try { if (session.IsAnonymous) { return; } using (var da = DAFactory.Get()) { if (packet.Type == ChangeRoommateType.POLL) { var lots = da.Roommates.GetAvatarsLots(session.AvatarId); foreach (var lot in lots) { if (lot.is_pending == 1) { var lotdb = da.Lots.Get(lot.lot_id); if (lotdb == null) { return; } session.Write(new ChangeRoommateRequest { Type = ChangeRoommateType.INVITE, AvatarId = lotdb.owner_id, LotLocation = lotdb.location }); } } } else if (packet.Type == ChangeRoommateType.ACCEPT) { var lot = da.Lots.GetByLocation(Context.ShardId, packet.LotLocation); if (lot == null) { Status(session, ChangeRoommateResponseStatus.LOT_DOESNT_EXIST); return; } if (da.Roommates.AcceptRoommateRequest(session.AvatarId, lot.lot_id)) { var lotDS = await DataService.Get <FSO.Common.DataService.Model.Lot>(packet.LotLocation); if (lotDS != null) { lotDS.Lot_RoommateVec = lotDS.Lot_RoommateVec.Add(session.AvatarId); } var lotOwned = da.LotClaims.GetByLotID(lot.lot_id); if (lotOwned != null) { var lotServer = LotServers.GetLotServerSession(lotOwned.owner); if (lotServer != null) { //immediately notify lot of new roommate lotServer.Write(new NotifyLotRoommateChange() { AvatarId = session.AvatarId, LotId = lot.lot_id, Change = Protocol.Gluon.Model.ChangeType.ADD_ROOMMATE }); } } var avatar = await DataService.Get <Avatar>(session.AvatarId); if (avatar != null) { avatar.Avatar_LotGridXY = packet.LotLocation; } Status(session, ChangeRoommateResponseStatus.ACCEPT_SUCCESS); return; } else { Status(session, ChangeRoommateResponseStatus.NO_INVITE_PENDING); return; } } else if (packet.Type == ChangeRoommateType.DECLINE) { var lot = da.Lots.GetByLocation(Context.ShardId, packet.LotLocation); if (lot == null) { Status(session, ChangeRoommateResponseStatus.LOT_DOESNT_EXIST); return; } if (da.Roommates.DeclineRoommateRequest(session.AvatarId, lot.lot_id)) { Status(session, ChangeRoommateResponseStatus.DECLINE_SUCCESS); return; } else { Status(session, ChangeRoommateResponseStatus.NO_INVITE_PENDING); return; } } else { //verify that requester is definitely a roommate in the target lot var ownedLot = da.Lots.GetByOwner(session.AvatarId); var myLots = da.Roommates.GetAvatarsLots(session.AvatarId); if (packet.Type == ChangeRoommateType.INVITE) { //is invitee roommate somewhere else? count lot roommates and check for max var targ = da.Avatars.Get(packet.AvatarId); if (targ == null) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); } var targLots = da.Roommates.GetAvatarsLots(packet.AvatarId); if (targLots.Count > 0) { Status(session, ChangeRoommateResponseStatus.ROOMIE_ELSEWHERE); //request already pending or otherwise return; } var lotr = myLots.FirstOrDefault(); DbLot lot = null; if (lotr != null) { lot = da.Lots.Get(lotr.lot_id); } if (lotr == null || lot == null) { Status(session, ChangeRoommateResponseStatus.LOT_DOESNT_EXIST); //what?? return; } if (lot.owner_id != session.AvatarId) //only an owner can add roommates { Status(session, ChangeRoommateResponseStatus.YOU_ARE_NOT_OWNER); return; } var myLotRoomies = da.Roommates.GetLotRoommates(lotr.lot_id); if (myLotRoomies.Count >= 8) { //if pending roommates put us over, cancel some of them. //assume first is oldest request var pending = myLotRoomies.FirstOrDefault(x => x.is_pending == 1); if (pending == null) { Status(session, ChangeRoommateResponseStatus.TOO_MANY_ROOMMATES); return; } else { da.Roommates.DeclineRoommateRequest(pending.avatar_id, pending.lot_id); } } //create roommate request in database if (!da.Roommates.Create(new DbRoommate { avatar_id = packet.AvatarId, lot_id = lotr.lot_id, is_pending = 1, permissions_level = 0 })) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); return; } //if online, notify roommate of pending request. var targetSession = Sessions.GetByAvatarId(packet.AvatarId); if (targetSession != null) { targetSession.Write(new ChangeRoommateRequest() { Type = ChangeRoommateType.INVITE, AvatarId = session.AvatarId, LotLocation = lot.location }); } Status(session, ChangeRoommateResponseStatus.INVITE_SUCCESS); return; //if not, we'll catch them when they log in later. } else if (packet.Type == ChangeRoommateType.KICK) { var lot = da.Lots.GetByLocation(Context.ShardId, packet.LotLocation); if (lot == null) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); return; } var roommates = da.Roommates.GetLotRoommates(lot.lot_id); if (roommates.Count(x => x.is_pending == 0) <= 1) { //we're the last roommate here. the lot must be closed. This will cause the lot to fall off-map. if (lot.owner_id != session.AvatarId) { //only the owner can delete their lot Status(session, ChangeRoommateResponseStatus.UNKNOWN); return; } //TODO: let user do this with their lot falling off map. for now they must use start fresh mode. Status(session, ChangeRoommateResponseStatus.UNKNOWN); return; } else if (roommates.Any(x => x.avatar_id == packet.AvatarId && x.is_pending == 0)) { //avatar can be removed from the lot. var selfDelete = false; if (session.AvatarId == packet.AvatarId) { //self deletes are allowed selfDelete = true; } else if (lot.owner_id != session.AvatarId) { //only the owner can kickout other roommates Status(session, ChangeRoommateResponseStatus.YOU_ARE_NOT_OWNER); return; } if (da.Roommates.RemoveRoommate(packet.AvatarId, lot.lot_id) == 0) { //nothing happened when we tried to remove this user's roommate status. Status(session, ChangeRoommateResponseStatus.YOU_ARE_NOT_ROOMMATE); return; } if (selfDelete && lot.owner_id == session.AvatarId) { //lot needs a new owner da.Lots.ReassignOwner(lot.lot_id); //database will assign oldest roommate as owner. } var lotDS = await DataService.Get <FSO.Common.DataService.Model.Lot>(packet.LotLocation); if (lotDS != null) { lotDS.Lot_RoommateVec = lotDS.Lot_RoommateVec.Remove(packet.AvatarId); var newLot = da.Lots.Get(lot.lot_id); lotDS.Lot_OwnerVec = ImmutableList.Create(newLot.owner_id); } //if online, notify the lot var lotOwned = da.LotClaims.GetByLotID(lot.lot_id); if (lotOwned != null) { var lotServer = LotServers.GetLotServerSession(lotOwned.owner); if (lotServer != null) { //immediately notify lot of new roommate lotServer.Write(new NotifyLotRoommateChange() { AvatarId = packet.AvatarId, LotId = lot.lot_id, Change = Protocol.Gluon.Model.ChangeType.REMOVE_ROOMMATE }); } } else { //try force the lot open var result = await Lots.TryFindOrOpen(lot.location, 0, session); } //TODO: if offline, force the lot to open so we can remove the kicked out roommate's objects. var avatar = await DataService.Get <Avatar>(packet.AvatarId); if (avatar != null) { avatar.Avatar_LotGridXY = 0; } //try to notify roommates foreach (var roomie in roommates) { var kickedMe = roomie.avatar_id == packet.AvatarId; if (roomie.is_pending == 0 && !(kickedMe && selfDelete) && session.AvatarId != roomie.avatar_id) { var targetSession = Sessions.GetByAvatarId(roomie.avatar_id); if (targetSession != null) { targetSession.Write(new ChangeRoommateResponse() { Type = (kickedMe)?ChangeRoommateResponseStatus.GOT_KICKED:ChangeRoommateResponseStatus.ROOMMATE_LEFT, Extra = packet.AvatarId }); } } } if (selfDelete) { Status(session, ChangeRoommateResponseStatus.SELFKICK_SUCCESS); return; } else { Status(session, ChangeRoommateResponseStatus.KICK_SUCCESS); return; } } else { //not roommate?? Status(session, ChangeRoommateResponseStatus.YOU_ARE_NOT_ROOMMATE); return; } //if target avatar is our avatar, we are moving out //if we are owner of the lot, set the new owner to the first (earliest) roommate entry in the database. //if we are the last person in the lot, the lot must be closed before doing this. //make sure all references are set to new owner! //remove roommate entry for target avatar. //update lot data service and avatar data service for targets. //if lot open, notify lot server of change (roommate add/remove AND new/same owner) //the lot will remove objects as necessary //future: if lot closed, special request to a lot server to quickly open an unjoinable instance of the lot to remove our objects. } } } } catch (Exception e) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); } }
/// <summary> /// The user is asking for some in RAM data /// </summary> /// <param name="session"></param> /// <param name="packet"></param> public async void Handle(IVoltronSession session, DataServiceWrapperPDU packet) { try //data service throws exceptions (SecurityException, etc) when invalid requests are made. These should not crash the server... { if (packet.Body is cTSONetMessageStandard) { var msg = (cTSONetMessageStandard)packet.Body; if (msg.ComplexParameter is cTSOTopicUpdateMessage) { var update = msg.ComplexParameter as cTSOTopicUpdateMessage; DataService.ApplyUpdate(update, session); return; } var type = MaskedStructUtils.FromID(packet.RequestTypeID); if (!msg.Parameter.HasValue) { return; } //Lookup the entity, then process the request and send the response var task = DataService.Get(type, msg.Parameter.Value); if (task != null) { var entity = await task; var serialized = DataService.SerializeUpdate(type, entity, msg.Parameter.Value); for (int i = 0; i < serialized.Count; i++) { object serial = serialized[i]; session.Write(new DataServiceWrapperPDU() { SendingAvatarID = packet.SendingAvatarID, RequestTypeID = packet.RequestTypeID, Body = serial }); } } } else if (packet.Body is cTSOTopicUpdateMessage) { //Client wants to update a value in the data service var update = packet.Body as cTSOTopicUpdateMessage; DataService.ApplyUpdate(update, session); List <uint> resultDotPath = new List <uint>(); foreach (var item in update.DotPath) { var ires = item; if (ires == 0x1095C1E1) { ires = 0x7EA285CD; //rewrite: filter id -> returns -> result list } resultDotPath.Add(ires); if (ires == packet.RequestTypeID) { break; } } var result = await DataService.SerializePath(resultDotPath.ToArray()); if (result != null) { session.Write(new DataServiceWrapperPDU() { SendingAvatarID = packet.SendingAvatarID, RequestTypeID = packet.RequestTypeID, Body = result }); } /*var task = DataService.Get(update.DotPath[0], update.DotPath[1]); * if(task != null) * { * var entity = await task; * * var serialized = DataService.SerializeUpdate(type, entity, msg.Parameter.Value); * }*/ /**/ } } catch (Exception e) { //SerializePath throws generic exceptions. //plus we don't want weird special cases crashing the whole server. LOG.Error(e, "Voltron DataService request failed: from avatar " + session.AvatarId + ", \n" + e.ToString()); } }
private void Status(IVoltronSession session, ChangeRoommateResponseStatus status) { session.Write(new ChangeRoommateResponse { Type = status }); }
public async void Handle(IVoltronSession session, ChangeRoommateRequest packet) { try { if (session.IsAnonymous) { return; } using (var da = DAFactory.Get()) { if (packet.Type == ChangeRoommateType.POLL) { var lots = da.Roommates.GetAvatarsLots(session.AvatarId); foreach (var lot in lots) { if (lot.is_pending == 1) { var lotdb = da.Lots.Get(lot.lot_id); if (lotdb == null) { return; } session.Write(new ChangeRoommateRequest { Type = ChangeRoommateType.INVITE, AvatarId = lotdb.owner_id ?? 0, LotLocation = lotdb.location }); } } } else if (packet.Type == ChangeRoommateType.ACCEPT) { var lot = da.Lots.GetByLocation(Context.ShardId, packet.LotLocation); if (lot == null) { Status(session, ChangeRoommateResponseStatus.LOT_DOESNT_EXIST); return; } if (da.Roommates.AcceptRoommateRequest(session.AvatarId, lot.lot_id)) { var lotDS = await DataService.Get <FSO.Common.DataService.Model.Lot>(packet.LotLocation); if (lotDS != null) { lotDS.Lot_RoommateVec = lotDS.Lot_RoommateVec.Add(session.AvatarId); } var lotOwned = da.LotClaims.GetByLotID(lot.lot_id); if (lotOwned != null) { var lotServer = LotServers.GetLotServerSession(lotOwned.owner); if (lotServer != null) { //immediately notify lot of new roommate lotServer.Write(new NotifyLotRoommateChange() { AvatarId = session.AvatarId, LotId = lot.lot_id, Change = Protocol.Gluon.Model.ChangeType.ADD_ROOMMATE }); } } var avatar = await DataService.Get <Avatar>(session.AvatarId); if (avatar != null) { avatar.Avatar_LotGridXY = packet.LotLocation; } Status(session, ChangeRoommateResponseStatus.ACCEPT_SUCCESS); return; } else { Status(session, ChangeRoommateResponseStatus.NO_INVITE_PENDING); return; } } else if (packet.Type == ChangeRoommateType.DECLINE) { var lot = da.Lots.GetByLocation(Context.ShardId, packet.LotLocation); if (lot == null) { Status(session, ChangeRoommateResponseStatus.LOT_DOESNT_EXIST); return; } if (da.Roommates.DeclineRoommateRequest(session.AvatarId, lot.lot_id)) { Status(session, ChangeRoommateResponseStatus.DECLINE_SUCCESS); return; } else { Status(session, ChangeRoommateResponseStatus.NO_INVITE_PENDING); return; } } else { //verify that requester is definitely a roommate in the target lot var ownedLot = da.Lots.GetByOwner(session.AvatarId); var myLots = da.Roommates.GetAvatarsLots(session.AvatarId); if (packet.Type == ChangeRoommateType.INVITE) { //is invitee roommate somewhere else? count lot roommates and check for max var targ = da.Avatars.Get(packet.AvatarId); if (targ == null) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); } var targLots = da.Roommates.GetAvatarsLots(packet.AvatarId); if (targLots.Count > 0) { Status(session, ChangeRoommateResponseStatus.ROOMIE_ELSEWHERE); //request already pending or otherwise return; } var lotr = myLots.FirstOrDefault(); DbLot lot = null; if (lotr != null) { lot = da.Lots.Get(lotr.lot_id); } if (lotr == null || lot == null) { Status(session, ChangeRoommateResponseStatus.LOT_DOESNT_EXIST); //what?? return; } if (lot.owner_id != session.AvatarId) //only an owner can add roommates { Status(session, ChangeRoommateResponseStatus.YOU_ARE_NOT_OWNER); return; } var myLotRoomies = da.Roommates.GetLotRoommates(lotr.lot_id); if (myLotRoomies.Count >= 8) { //if pending roommates put us over, cancel some of them. //assume first is oldest request var pending = myLotRoomies.FirstOrDefault(x => x.is_pending == 1); if (pending == null) { Status(session, ChangeRoommateResponseStatus.TOO_MANY_ROOMMATES); return; } else { da.Roommates.DeclineRoommateRequest(pending.avatar_id, pending.lot_id); } } //create roommate request in database if (!da.Roommates.Create(new DbRoommate { avatar_id = packet.AvatarId, lot_id = lotr.lot_id, is_pending = 1, permissions_level = 0 })) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); return; } //if online, notify roommate of pending request. var targetSession = Sessions.GetByAvatarId(packet.AvatarId); if (targetSession != null) { targetSession.Write(new ChangeRoommateRequest() { Type = ChangeRoommateType.INVITE, AvatarId = session.AvatarId, LotLocation = lot.location }); } Status(session, ChangeRoommateResponseStatus.INVITE_SUCCESS); return; //if not, we'll catch them when they log in later. } else if (packet.Type == ChangeRoommateType.KICK) { var result = await TryKick(packet.LotLocation, session.AvatarId, packet.AvatarId); Status(session, result); } } } } catch (Exception e) { Status(session, ChangeRoommateResponseStatus.UNKNOWN); } }
public void Handle(IVoltronSession session, BulletinRequest message) { if (session.IsAnonymous) //CAS users can't do this. { return; } try { using (var da = DA.Get()) { switch (message.Type) { case BulletinRequestType.GET_MESSAGES: //when a user logs in they will send this request to recieve all messages after their last recieved message. { var msgs = da.BulletinPosts.GetByNhoodId(message.TargetNHood, 0); session.Write(new BulletinResponse() { Type = BulletinResponseType.MESSAGES, Messages = msgs.Select(x => ToItem(x)).ToArray() }); return; } case BulletinRequestType.DELETE_MESSAGE: //delete bulletin message { //check if either we own this post or we're admin var post = da.BulletinPosts.Get(message.Value); if (post == null) { session.Write(Code(BulletinResponseType.FAIL_MESSAGE_DOESNT_EXIST)); return; //doesn't exist } if (post.avatar_id != session.AvatarId) { //not the owner of this post. are we an admin? var myAva = da.Avatars.Get(session.AvatarId); if (myAva == null || myAva.moderation_level == 0) { session.Write(Code(BulletinResponseType.FAIL_CANT_DELETE)); return; } } da.BulletinPosts.SoftDelete(message.Value); session.Write(Code(BulletinResponseType.SUCCESS)); return; } case BulletinRequestType.PROMOTE_MESSAGE: //promote message to the mayor channel { //check if either we're mayor of this post's nhood or we're admin //also the post should like, exist. var post = da.BulletinPosts.Get(message.Value); if (post == null) { session.Write(Code(BulletinResponseType.FAIL_MESSAGE_DOESNT_EXIST)); return; } if ((post.flags & 1) > 0) { session.Write(Code(BulletinResponseType.FAIL_ALREADY_PROMOTED)); return; //already promoted. } if (post.type != DbBulletinType.community) { session.Write(Code(BulletinResponseType.FAIL_BAD_PERMISSION)); return; } var postNhood = da.Neighborhoods.Get((uint)post.neighborhood_id); if (postNhood == null || postNhood.mayor_id != session.AvatarId) { session.Write(Code(BulletinResponseType.FAIL_NOT_MAYOR)); return; //no permission } post.flags |= 1; da.BulletinPosts.SetTypeFlag(message.Value, DbBulletinType.mayor, (int)post.flags); session.Write(Code(BulletinResponseType.SUCCESS)); return; } case BulletinRequestType.CAN_POST_MESSAGE: case BulletinRequestType.CAN_POST_SYSTEM_MESSAGE: case BulletinRequestType.POST_SYSTEM_MESSAGE: case BulletinRequestType.POST_MESSAGE: { var type = (message.Type == BulletinRequestType.POST_SYSTEM_MESSAGE) ? DbBulletinType.system : DbBulletinType.community; var myAva = da.Avatars.Get(session.AvatarId); if (myAva == null) { session.Write(Code(BulletinResponseType.FAIL_UNKNOWN)); return; } var now = Epoch.Now; if (now - myAva.move_date < MOVE_LIMIT_PERIOD && myAva.moderation_level == 0) { session.Write(Code(BulletinResponseType.SEND_FAIL_JUST_MOVED)); return; } var myLotID = da.Roommates.GetAvatarsLots(session.AvatarId)?.FirstOrDefault(); DbLot myLot = (myLotID != null) ? da.Lots.Get(myLotID.lot_id) : null; if (myLot == null || myLot.neighborhood_id != message.TargetNHood || message.Type == BulletinRequestType.POST_SYSTEM_MESSAGE || message.Type == BulletinRequestType.CAN_POST_SYSTEM_MESSAGE) { //need to live in this nhood to post //if we're an admin we can ignore this if (myAva.moderation_level == 0) { session.Write(Code(BulletinResponseType.SEND_FAIL_NON_RESIDENT)); return; } } var postNhood = da.Neighborhoods.Get((uint)message.TargetNHood); if (postNhood == null) { session.Write(Code(BulletinResponseType.FAIL_UNKNOWN)); return; } if (session.AvatarId == postNhood.mayor_id && type == DbBulletinType.community) { type = DbBulletinType.mayor; } //are we nhood gameplay banned? var ban = da.Neighborhoods.GetNhoodBan(myAva.user_id); if (ban != null) { session.Write(new BulletinResponse { Type = BulletinResponseType.SEND_FAIL_GAMEPLAY_BAN, Message = ban.ban_reason, BanEndDate = ban.end_date }); return; } //verify post frequency var last = da.BulletinPosts.LastUserPost(myAva.user_id, message.TargetNHood); int frequency = 0; switch (type) { case DbBulletinType.mayor: frequency = POST_FREQ_LIMIT_MAYOR; break; case DbBulletinType.community: frequency = POST_FREQ_LIMIT; break; } if (Epoch.Now - (last?.date ?? 0) < frequency) { session.Write(Code(BulletinResponseType.SEND_FAIL_TOO_FREQUENT)); return; } if (message.Type == BulletinRequestType.CAN_POST_MESSAGE || message.Type == BulletinRequestType.CAN_POST_SYSTEM_MESSAGE) { session.Write(Code(BulletinResponseType.SUCCESS)); return; } int?lotID = null; if (message.LotID != 0) { //verify the lot ID if one is included var lot = da.Lots.GetByLocation(Context.ShardId, message.LotID); if (lot == null) { session.Write(Code(BulletinResponseType.SEND_FAIL_INVALID_LOT)); return; } lotID = (int)message.LotID; } if (message.Message.Length == 0 || message.Message.Length > 1000) { session.Write(Code(BulletinResponseType.SEND_FAIL_INVALID_MESSAGE)); return; } if (message.Title.Length == 0 || message.Title.Length > 64) { session.Write(Code(BulletinResponseType.SEND_FAIL_INVALID_TITLE)); return; } var db = new DbBulletinPost() { avatar_id = session.AvatarId, date = Epoch.Now, title = message.Title, body = message.Message, flags = 0, lot_id = lotID, neighborhood_id = (int)message.TargetNHood, type = type }; try { db.bulletin_id = da.BulletinPosts.Create(db); } catch (Exception e) { LOG.Error(e.ToString()); session.Write(Code(BulletinResponseType.FAIL_UNKNOWN)); return; } session.Write(new BulletinResponse() { Type = BulletinResponseType.SEND_SUCCESS, Messages = new BulletinItem[] { ToItem(db) } }); } break; } } } catch (Exception e) { LOG.Error(e.ToString()); session.Write(Code(BulletinResponseType.FAIL_UNKNOWN)); return; } }