public void SendStateEmail(IDA da, MailHandler mail, DbNeighborhood nhood, DbElectionCycle cycle, uint avatarID) { if (!da.Elections.TryRegisterMail(new DbElectionCycleMail() { avatar_id = avatarID, cycle_id = cycle.cycle_id, cycle_state = cycle.current_state })) { return; } var endDate = cycle.end_date; switch (cycle.current_state) { case DbElectionCycleState.nomination: endDate -= 60 * 60 * 24 * 3; //nomination ends 3 days before end of cycle mail.SendSystemEmail("f116", (int)NeighMailStrings.NominateSubject, (int)NeighMailStrings.Nominate, 1, MessageSpecialType.Nominate, endDate, avatarID, nhood.name, endDate.ToString()); break; case DbElectionCycleState.election: mail.SendSystemEmail("f116", (int)NeighMailStrings.VoteSubject, (int)NeighMailStrings.Vote, 1, MessageSpecialType.Vote, endDate, avatarID, nhood.name, endDate.ToString()); break; case DbElectionCycleState.failsafe: mail.SendSystemEmail("f116", (int)NeighMailStrings.FailsafeSubject, (int)NeighMailStrings.Failsafe, 1, MessageSpecialType.Normal, endDate, avatarID, nhood.name); break; case DbElectionCycleState.shutdown: if (Context.Config.Neighborhoods.Election_Free_Vote) { mail.SendSystemEmail("f116", (int)NeighMailStrings.FreeVoteSubject, (int)NeighMailStrings.FreeVote, 1, MessageSpecialType.FreeVote, endDate, avatarID, nhood.name, endDate.ToString()); } break; case DbElectionCycleState.ended: var winner = da.Avatars.Get(nhood.mayor_id ?? 0)?.name; if (winner == null) { return; } mail.SendSystemEmail("f116", (int)NeighMailStrings.ElectionOverSubject, (int)NeighMailStrings.ElectionOver, 1, MessageSpecialType.Normal, endDate, avatarID, winner, nhood.name, "someone else idk"); break; } }
public static void SetElectionCycle(Neighborhood nhood, DbElectionCycle cycle) { if (cycle == null) { return; } var result = new ElectionCycle() { ElectionCycle_CurrentState = (byte)cycle.current_state, ElectionCycle_ElectionType = (byte)cycle.election_type, ElectionCycle_EndDate = cycle.end_date, ElectionCycle_StartDate = cycle.start_date }; nhood.Neighborhood_ElectionCycle = result; }
public void BroadcastNhoodState(IDA da, MailHandler mail, DbNeighborhood nhood, DbElectionCycle cycle) { var all = Sessions.Clone(); foreach (var session in all.OfType <IVoltronSession>()) { if (session.IsAnonymous) { continue; } var myLotID = da.Roommates.GetAvatarsLots(session.AvatarId).FirstOrDefault(); var myLot = (myLotID == null) ? null : da.Lots.Get(myLotID.lot_id); if (myLot != null && myLot.neighborhood_id == nhood.neighborhood_id) { SendStateEmail(da, mail, nhood, cycle, session.AvatarId); } } }
public async Task ChangeElectionState(IDA da, DbNeighborhood nhood, DbElectionCycle cycle, DbElectionCycleState state) { var now = Epoch.Now; var mail = Kernel.Get <MailHandler>(); switch (state) { case DbElectionCycleState.nomination: //start nominations for this cycle. var endDate = cycle.end_date - 60 * 60 * 24 * 3; //nomination ends 3 days before end of cycle SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.NominateSubject, (int)NeighBulletinStrings.Nominate, endDate, nhood.name, endDate.ToString()); break; case DbElectionCycleState.election: //end nominations. choose the candidate sims. var cycleNoms = da.Elections.GetCycleVotes(cycle.cycle_id, DbElectionVoteType.nomination); var toRemove = da.Elections.GetCandidates(cycle.cycle_id).ToDictionary(x => x.candidate_avatar_id); if (cycleNoms.Count > 0) { var grouped = cycleNoms.GroupBy(x => x.target_avatar_id).OrderByDescending(x => x.Count()); var selected = 0; var candidates = new List <IGrouping <uint, DbElectionVote> >(); foreach (var winner in grouped) { //verify the winners are still alive and still in this neighborhood string name = null; DbElectionCandidate cand; if (toRemove.TryGetValue(winner.Key, out cand) && VerifyCanBeMayor(da, winner.Key, (uint)nhood.neighborhood_id, now, ref name)) { if (cand.state != DbCandidateState.running) { continue; //user must have accepted their nomination } toRemove.Remove(winner.Key); mail.SendSystemEmail("f116", (int)NeighMailStrings.RunningForMayorSubject, (int)NeighMailStrings.RunningForMayor, 1, MessageSpecialType.Normal, cycle.end_date, winner.Key, nhood.name, cand.comment, cycle.end_date.ToString()); candidates.Add(winner); if (++selected >= 5) { break; } } } foreach (var remove in toRemove) { da.Elections.DeleteCandidate(remove.Value.election_cycle_id, remove.Value.candidate_avatar_id); if (remove.Value.state == DbCandidateState.running) { mail.SendSystemEmail("f116", (int)NeighMailStrings.TooFewNominationsSubject, (int)NeighMailStrings.TooFewNominations, 1, MessageSpecialType.Normal, cycle.end_date, remove.Key, nhood.name, cycle.end_date.ToString()); } else { mail.SendSystemEmail("f116", (int)NeighMailStrings.NominationNotAcceptedSubject, (int)NeighMailStrings.NominationNotAccepted, 1, MessageSpecialType.Normal, cycle.end_date, remove.Key, nhood.name, cycle.end_date.ToString()); } } if (selected == 0) { state = DbElectionCycleState.failsafe; break; } //email will be sent by event system. make a bulletin post too SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.VoteSubject, (int)NeighBulletinStrings.Vote, cycle.end_date, nhood.name, string.Join("\n", candidates.Select(x => "- " + (da.Avatars.Get(x.Key)?.name ?? "(unknown)"))), cycle.end_date.ToString()); } else { state = DbElectionCycleState.failsafe; } break; case DbElectionCycleState.ended: //end election. if there are any candidates, set the mayor to the one that recieved the most votes. var cycleVotes = da.Elections.GetCycleVotes(cycle.cycle_id, DbElectionVoteType.vote); if (cycleVotes.Count > 0) { var grouped = cycleVotes.GroupBy(x => x.target_avatar_id).OrderByDescending(x => x.Count()).ToList(); //verify the winner is still alive and still in this neighborhood string name = ""; while (grouped.Count > 0 && !VerifyCanBeMayor(da, grouped[0].Key, (uint)nhood.neighborhood_id, now, ref name)) { grouped.RemoveAt(0); } if (grouped.Count == 0) { state = DbElectionCycleState.failsafe; if (nhood.mayor_id != null) { await SetMayor(da, 0, (uint)nhood.neighborhood_id); } break; } var winner = grouped[0]; //we have a winner da.Elections.SetCandidateState(new DbElectionCandidate() { election_cycle_id = cycle.cycle_id, candidate_avatar_id = winner.Key, state = DbCandidateState.won }); await SetMayor(da, winner.Key, (uint)nhood.neighborhood_id); mail.SendSystemEmail("f116", (int)NeighMailStrings.YouWinSubject, (int)NeighMailStrings.YouWin, 1, MessageSpecialType.Normal, 0, winner.Key, nhood.name, winner.Count().ToString()); //tell the losers they lost grouped.RemoveAt(0); var placement = 2; foreach (var loser in grouped) { da.Elections.SetCandidateState(new DbElectionCandidate() { election_cycle_id = cycle.cycle_id, candidate_avatar_id = loser.Key, state = DbCandidateState.lost }); placement++; } int runnerI = 2; SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.ElectionOverSubject, (int)NeighBulletinStrings.ElectionOver, cycle.end_date, (da.Avatars.Get(winner.Key)?.name ?? "(unknown)"), nhood.name, string.Join("\n", grouped.Select(x => (runnerI++).ToString() + ". " + (da.Avatars.Get(x.Key)?.name ?? "(unknown)")))); } else { state = DbElectionCycleState.failsafe; if (nhood.mayor_id != null) { await SetMayor(da, 0, (uint)nhood.neighborhood_id); } } break; } da.Elections.UpdateCycleState(cycle.cycle_id, state); if (state == DbElectionCycleState.failsafe && state != cycle.current_state) { SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.FailsafeSubject, (int)NeighBulletinStrings.Failsafe, 0, nhood.name); } cycle.current_state = state; if (StateHasEmail(state)) { BroadcastNhoodState(da, mail, nhood, cycle); } }
public async Task TickNeighborhoods(DateTime now) { var config = Context.Config.Neighborhoods; //process the neighbourhoods for this city var endDate = new DateTime(now.Year, now.Month, 1).AddMonths(1); if (config.Election_Week_Align) { endDate = FindLastWeek(endDate); } var timeToNextMonth = (endDate - now); var ds = Kernel.Get <IDataService>(); var epochNow = Epoch.FromDate(now); using (var da = DAFactory.Get()) { var midnight = LotVisitUtils.Midnight(); //gets this morning's midnight (basically current date, with day reset) var activityBeginning = midnight - new TimeSpan(7, 0, 0, 0); var range = da.LotVisits.StreamBetweenPlusNhood(Context.ShardId, activityBeginning, midnight); var enumerator = range.GetEnumerator(); var nhoodHours = new Dictionary <uint, double>(); while (enumerator.MoveNext()) { var visit = enumerator.Current; var span = LotVisitUtils.CalculateDateOverlap(activityBeginning, midnight, visit.time_created, visit.time_closed.Value); if (nhoodHours.ContainsKey(visit.neighborhood_id)) { nhoodHours[visit.neighborhood_id] += span.TotalMinutes; } else { nhoodHours.Add(visit.neighborhood_id, span.TotalMinutes); } } // var nhoodOrder = nhoodHours.OrderByDescending(x => x.Value); var nhoods = da.Neighborhoods.All(Context.ShardId); foreach (var nhood in nhoods) { var nhoodDS = await ds.Get <Neighborhood>((uint)nhood.neighborhood_id); if (nhoodDS == null) { continue; } //placement within the top neighbourhoods for activity var placement = nhoodOrder.ToList().FindIndex(x => x.Key == nhood.neighborhood_id); if (placement == -1) { placement = nhoods.Count; } nhoodDS.Neighborhood_ActivityRating = (uint)placement + 1; //is there an active cycle for this neighbourhood? var stillActive = false; if (nhood.election_cycle_id != null) { var cycle = da.Elections.GetCycle(nhood.election_cycle_id.Value); if (cycle != null) { if (cycle.current_state == DbElectionCycleState.shutdown || cycle.current_state == DbElectionCycleState.failsafe) { long timeToEnd = (long)cycle.end_date - epochNow; if (timeToEnd <= 0 && nhood.mayor_id != null) { await SetMayor(da, 0, (uint)nhood.neighborhood_id); } stillActive = epochNow < cycle.end_date; //can't switch eligibility til we end } else if (cycle.current_state < DbElectionCycleState.ended) { var active = (epochNow >= cycle.start_date && epochNow < cycle.end_date); long timeToEnd = (long)cycle.end_date - epochNow; DbElectionCycleState targetState; if (timeToEnd <= 0) { targetState = DbElectionCycleState.ended; } else if (timeToEnd <= 60 * 60 * 24 * 3) //last 3 days are the full election { targetState = DbElectionCycleState.election; } else //all other time is the nomination { targetState = DbElectionCycleState.nomination; } if (targetState != cycle.current_state) { await ChangeElectionState(da, nhood, cycle, targetState); nhoodDS.Neighborhood_ElectionCycle.ElectionCycle_CurrentState = (byte)targetState; } //important: if we are in failsafe mode we can't switch eligibility or start a new cycle. if (cycle.current_state != DbElectionCycleState.ended) { stillActive = true; } } } } //do we need to start a new cycle? if (!stillActive && timeToNextMonth.TotalDays < 7 && (nhood.flag & 1) == 0) { //update eligibility if ((nhood.flag & 2) > 0) { //not eligibile for elections (temp) //is our placement within bounds? if (placement != -1 && placement < config.Mayor_Elegibility_Limit) { //make us eligible. nhood.flag &= ~(uint)2; nhoodDS.Neighborhood_Flag = nhood.flag; da.Neighborhoods.UpdateFlag((uint)nhood.neighborhood_id, nhood.flag); SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.ElectionBeginSubject, (int)NeighBulletinStrings.ElectionBegin, 0, nhood.name, config.Mayor_Elegibility_Limit.ToString()); } } else { //is our placement outwith bounds? if (placement == -1 || placement >= config.Mayor_Elegilility_Falloff) { //make us ineligible. nhood.flag |= 2; nhoodDS.Neighborhood_Flag = nhood.flag; da.Neighborhoods.UpdateFlag((uint)nhood.neighborhood_id, nhood.flag); //start a shutdown cycle var dbCycle = new DbElectionCycle { current_state = DbElectionCycleState.shutdown, election_type = DbElectionCycleType.shutdown, start_date = Epoch.FromDate(midnight), end_date = Epoch.FromDate(endDate) }; var cycleID = da.Elections.CreateCycle(dbCycle); nhoodDS.Neighborhood_ElectionCycle = new ElectionCycle() { ElectionCycle_CurrentState = (byte)dbCycle.current_state, ElectionCycle_ElectionType = (byte)dbCycle.election_type, ElectionCycle_StartDate = dbCycle.start_date, ElectionCycle_EndDate = dbCycle.end_date }; da.Neighborhoods.UpdateCycle((uint)nhood.neighborhood_id, cycleID); SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.ElectionCancelledSubject, (int)NeighBulletinStrings.ElectionCancelled, 0, nhood.name); } } var eligible = (nhood.flag & 2) == 0; if (eligible || nhood.election_cycle_id == null) { //yes var dbCycle = new DbElectionCycle { current_state = (eligible) ? DbElectionCycleState.nomination : DbElectionCycleState.shutdown, election_type = (eligible) ? DbElectionCycleType.election : DbElectionCycleType.shutdown, start_date = Epoch.FromDate(midnight), end_date = Epoch.FromDate(endDate) }; var cycleID = da.Elections.CreateCycle(dbCycle); nhoodDS.Neighborhood_ElectionCycle = new ElectionCycle() { ElectionCycle_CurrentState = (byte)dbCycle.current_state, ElectionCycle_ElectionType = (byte)dbCycle.election_type, ElectionCycle_StartDate = dbCycle.start_date, ElectionCycle_EndDate = dbCycle.end_date }; da.Neighborhoods.UpdateCycle((uint)nhood.neighborhood_id, cycleID); if (eligible) { //notify current mayor if (nhood.mayor_id != null) { var mail = Kernel.Get <MailHandler>(); mail.SendSystemEmail("f116", (int)NeighMailStrings.TermLengthSubject, (int)NeighMailStrings.TermLength, 1, MessageSpecialType.Nominate, dbCycle.end_date, nhood.mayor_id.Value, nhood.name, dbCycle.end_date.ToString()); } //post to bulletin var nomEndDate = dbCycle.end_date - 60 * 60 * 24 * 3; //nomination ends 3 days before end of cycle SendBulletinPost(da, nhood.neighborhood_id, "f123", (int)NeighBulletinStrings.NominateSubject, (int)NeighBulletinStrings.Nominate, nomEndDate, nhood.name, nomEndDate.ToString()); } } } } } }
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 void BroadcastNhoodState(IDA da, MailHandler mail, DbNeighborhood nhood, DbElectionCycle cycle) { var all = Sessions.Clone(); foreach (var session in all.OfType <IVoltronSession>()) { if (session.IsAnonymous) { continue; } var myLotID = da.Roommates.GetAvatarsLots(session.AvatarId).FirstOrDefault(); var myLot = (myLotID == null) ? null : da.Lots.Get(myLotID.lot_id); var free = da.Elections.GetFreeVote(session.AvatarId); var nhoodID = (int)(myLot?.neighborhood_id ?? 0); if (free != null) { nhoodID = free.neighborhood_id; //enrolled to a free vote. receive vote mail for that neighborhood } if (myLot != null && nhoodID == nhood.neighborhood_id) { SendStateEmail(da, mail, nhood, cycle, session.AvatarId); } } }
protected Neighborhood HydrateOne(DbNeighborhood nhood, List <uint> avatars, List <uint> lots, uint townHallLoc, DbElectionCycle cycle, List <DbLotVisitNhood> visits, List <KeyValuePair <uint, double> > order) { var rand = new Random(); var result = new Neighborhood { Id = (uint)nhood.neighborhood_id, Neighborhood_ActivityRating = 1, Neighborhood_AvatarCount = (uint)avatars.Count, Neighborhood_CenterGridXY = nhood.location, Neighborhood_Color = nhood.color, Neighborhood_Description = nhood.description, Neighborhood_ElectedDate = nhood.mayor_elected_date, Neighborhood_ElectionCycle = new ElectionCycle(), Neighborhood_Flag = nhood.flag, Neighborhood_IconURL = nhood.icon_url ?? "", Neighborhood_LotCount = (uint)lots.Count, Neighborhood_MayorID = nhood.mayor_id ?? 0, Neighborhood_Name = nhood.name, Neighborhood_TownHallXY = townHallLoc, }; SetElectionCycle(result, cycle); SetTop10s(result, avatars, visits, order); return(result); }