diff --git a/Application/API/EventAPI.cs b/Application/API/EventAPI.cs index 20aa1c510..f1ef14076 100644 --- a/Application/API/EventAPI.cs +++ b/Application/API/EventAPI.cs @@ -10,66 +10,69 @@ namespace IW4MAdmin.Application.API { class EventApi : IEventApi { - Queue Events = new Queue(); - DateTime LastFlagEvent; - static string[] FlaggedMessageContains = - { - " wh ", - "hax", - "cheat", - " hack ", - "aim", - "wall", - "cheto", - "hak", - "bot" - }; - int FlaggedMessageCount; + private const int MaxEvents = 32; + private Queue RecentEvents = new Queue(); - public Queue GetEvents() => Events; + public IEnumerable GetEvents(bool shouldConsume) + { + var eventList = RecentEvents.ToArray(); + + // clear queue if events should be consumed + if (shouldConsume) + { + RecentEvents.Clear(); + } + + return eventList; + } public void OnServerEvent(object sender, GameEvent E) { - if (E.Type == GameEvent.EventType.Say && E.Origin.Level < Player.Permission.Trusted) + // don't want to clog up the api with unknown events + if (E.Type == GameEvent.EventType.Unknown) + return; + + var apiEvent = new EventInfo() { - bool flaggedMessage = false; - foreach (string msg in FlaggedMessageContains) - flaggedMessage = flaggedMessage ? flaggedMessage : E.Data.ToLower().Contains(msg); - - if (flaggedMessage) - FlaggedMessageCount++; - - if (FlaggedMessageCount > 3) + ExtraInfo = E.Extra?.ToString() ?? E.Data, + OwnerEntity = new EntityInfo() { - if (Events.Count > 20) - Events.Dequeue(); - - FlaggedMessageCount = 0; - - E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_REPORT"]).Wait(5000); - Events.Enqueue(new EventInfo( - EventInfo.EventType.ALERT, - EventInfo.EventVersion.IW4MAdmin, - "Chat indicates there may be a cheater", - "Alert", - E.Owner.Hostname, "")); - } - - if ((DateTime.UtcNow - LastFlagEvent).Minutes >= 3) + Name = E.Owner.Hostname, + Id = E.Owner.GetHashCode() + }, + OriginEntity = E.Origin == null ? null : new EntityInfo() { - FlaggedMessageCount = 0; - LastFlagEvent = DateTime.Now; - } - } + Id = E.Origin.ClientId, + Name = E.Origin.Name + }, + TargetEntity = E.Target == null ? null : new EntityInfo() + { + Id = E.Target.ClientId, + Name = E.Target.Name + }, + EventType = new EntityInfo() + { + Id = (int)E.Type, + Name = E.Type.ToString() + }, + EventTime = E.Time + }; - if (E.Type == GameEvent.EventType.Report) - { - Events.Enqueue(new EventInfo( - EventInfo.EventType.ALERT, - EventInfo.EventVersion.IW4MAdmin, - $"**{E.Origin.Name}** has reported **{E.Target.Name}** for: {E.Data.Trim()}", - E.Target.Name, E.Origin.Name, "")); - } + // add the new event to the list + AddNewEvent(apiEvent); + } + + /// + /// Adds event to the list and removes first added if reached max capacity + /// + /// EventInfo to add + private void AddNewEvent(EventInfo info) + { + // remove the first added event + if (RecentEvents.Count >= MaxEvents) + RecentEvents.Dequeue(); + + RecentEvents.Enqueue(info); } } } diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index 834697fa5..4219a19d6 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.EventParsers { Type = GameEvent.EventType.JoinTeam, Data = cleanedEventLine, - //Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), + Origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong()), Owner = server }; } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 75bd7452f..a1d00bf24 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -34,13 +34,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Manager = mgr; } - ~StatManager() - { - Servers.Clear(); - Log = null; - Servers = null; - } - public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId]; public async Task> GetTopStats(int start, int count) @@ -445,6 +438,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers switch (penalty.ClientPenalty) { case Penalty.PenaltyType.Ban: + if (attacker.Level == Player.Permission.Banned) + break; await saveLog(); await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() { @@ -461,7 +456,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers }); break; case Penalty.PenaltyType.Flag: - if (attacker.Level == Player.Permission.Flagged) + if (attacker.Level != Player.Permission.User) break; await saveLog(); var e = new GameEvent() diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index a29d0dfa5..a86ede8c2 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -65,8 +65,6 @@ namespace IW4MAdmin.Plugins.Stats break; case GameEvent.EventType.Ban: break; - case GameEvent.EventType.Remote: - break; case GameEvent.EventType.Unknown: break; case GameEvent.EventType.Report: @@ -83,8 +81,6 @@ namespace IW4MAdmin.Plugins.Stats if (!E.Owner.CustomCallback) await Manager.AddStandardKill(E.Origin, E.Target); break; - case GameEvent.EventType.Death: - break; case GameEvent.EventType.Damage: // if (!E.Owner.CustomCallback) Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode()); diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index eb26568c8..715703052 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -117,9 +117,9 @@ namespace SharedLibraryCore.Commands { if (E.Origin.Level > E.Target.Level) { - E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); await E.Target.Kick(E.Data, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"]}"); + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); } else await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"]} {E.Target.Name}"); @@ -179,6 +179,14 @@ namespace SharedLibraryCore.Commands { await E.Target.TempBan(Message, length, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}"); + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent() + { + Type = GameEvent.EventType.TempBan, + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = E.Owner + }); } else await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}"); @@ -209,6 +217,15 @@ namespace SharedLibraryCore.Commands { await E.Target.Ban(E.Data, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"]}"); + + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent() + { + Type = GameEvent.EventType.Ban, + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = E.Owner + }); } else await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"]} {E.Target.Name}"); @@ -753,6 +770,16 @@ namespace SharedLibraryCore.Commands E.Target.Level = Player.Permission.User; await E.Owner.Manager.GetClientService().Update(E.Target); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}"); + + E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent() + { + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = E.Owner, + Type = GameEvent.EventType.Unflag + }); + } else diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index 9b4bdf6ca..c50bcaf1c 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -17,6 +17,7 @@ namespace SharedLibraryCore.Database public DbSet AliasLinks { get; set; } public DbSet Penalties { get; set; } public DbSet EFMeta { get; set; } + public DbSet EFChangeHistory { get; set; } private static string _ConnectionString; @@ -79,6 +80,11 @@ namespace SharedLibraryCore.Database .OnDelete(DeleteBehavior.Restrict); }); + modelBuilder.Entity(ent => + { + ent.HasIndex(a => a.IPAddress); + }); + // force full name for database conversion modelBuilder.Entity().ToTable("EFClients"); modelBuilder.Entity().ToTable("EFAlias"); diff --git a/SharedLibraryCore/Database/Models/EFChangeHistory.cs b/SharedLibraryCore/Database/Models/EFChangeHistory.cs new file mode 100644 index 000000000..0fc9794ab --- /dev/null +++ b/SharedLibraryCore/Database/Models/EFChangeHistory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace SharedLibraryCore.Database.Models +{ + /// + /// This class models the change to different entities + /// + public class EFChangeHistory : SharedEntity + { + public enum ChangeType + { + Permission + } + + [Key] + public int ChangeHistoryId { get; set; } + public int OriginEntityId { get; set; } + public int TargetEntityId { get; set; } + public ChangeType TypeOfChange { get; set; } + public DateTime TimeChanged { get; set; } = DateTime.UtcNow; + [MaxLength(128)] + public string Comment { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/EntityInfo.cs b/SharedLibraryCore/Dtos/EntityInfo.cs new file mode 100644 index 000000000..e157a35bd --- /dev/null +++ b/SharedLibraryCore/Dtos/EntityInfo.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos +{ + /// + /// This class holds the basic info for api entities + /// + public class EntityInfo + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/EventInfo.cs b/SharedLibraryCore/Dtos/EventInfo.cs index c8079182e..1c49fce6e 100644 --- a/SharedLibraryCore/Dtos/EventInfo.cs +++ b/SharedLibraryCore/Dtos/EventInfo.cs @@ -1,39 +1,19 @@ using System; +using static SharedLibraryCore.GameEvent; namespace SharedLibraryCore.Dtos { + /// + /// This class wraps the information related to a generated event for the API + /// public class EventInfo { - public EventInfo(EventType Ty, EventVersion V, string M, string T, string O, string Ta) - { - Type = Ty; - Version = V; - Message = System.Web.HttpUtility.HtmlEncode(M); - Title = T; - Origin = System.Web.HttpUtility.HtmlEncode(O); - Target = System.Web.HttpUtility.HtmlEncode(Ta); - - ID = Math.Abs(DateTime.Now.GetHashCode()); - } - - public enum EventType - { - NOTIFICATION, - STATUS, - ALERT, - } - - public enum EventVersion - { - IW4MAdmin - } - - public EventType Type; - public EventVersion Version; - public string Message; - public string Title; - public string Origin; - public string Target; - public int ID; + public EntityInfo OriginEntity { get; set; } + public EntityInfo TargetEntity { get; set; } + public EntityInfo EventType { get; set; } + public EntityInfo OwnerEntity { get; set; } + public DateTime EventTime { get; set; } + public string ExtraInfo { get; set; } + public string Id { get; private set; } = Guid.NewGuid().ToString(); } } \ No newline at end of file diff --git a/SharedLibraryCore/Event.cs b/SharedLibraryCore/Event.cs index 0233562a7..a8dae929e 100644 --- a/SharedLibraryCore/Event.cs +++ b/SharedLibraryCore/Event.cs @@ -8,36 +8,37 @@ namespace SharedLibraryCore { public enum EventType { - //FROM SERVER + Unknown, + + // events "generated" by the server Start, Stop, Connect, - // this is for IW5 compatibility Join, + Quit, Disconnect, - Say, - MapChange, MapEnd, + MapChange, - //FROM ADMIN - Broadcast, - Tell, - Kick, - Ban, - Remote, - Unknown, - - //FROM PLAYER + // events "generated" by clients + Say, Report, Flag, + Unflag, + Kick, + TempBan, + Ban, Command, - // FROM GAME + // events "generated" by IW4MAdmin + Broadcast, + Tell, + + // events "generated" by script/log ScriptDamage, ScriptKill, - Kill, Damage, - Death, + Kill, JoinTeam, } diff --git a/SharedLibraryCore/Interfaces/IEventApi.cs b/SharedLibraryCore/Interfaces/IEventApi.cs index 62b21445e..bd2128a0f 100644 --- a/SharedLibraryCore/Interfaces/IEventApi.cs +++ b/SharedLibraryCore/Interfaces/IEventApi.cs @@ -5,7 +5,17 @@ namespace SharedLibraryCore.Interfaces { public interface IEventApi { + /// + /// Processes event from server as event info + /// + /// Object state from Delegate method call + /// Event to process void OnServerEvent(object sender, GameEvent E); - Queue GetEvents(); + /// + /// Get list of recent events + /// + /// specify wether the request should clear all events after retrieving + /// List of recent event + IEnumerable GetEvents(bool shouldConsume); } } diff --git a/SharedLibraryCore/Migrations/20180614014303_IndexForEFAlias.Designer.cs b/SharedLibraryCore/Migrations/20180614014303_IndexForEFAlias.Designer.cs new file mode 100644 index 000000000..9d25be797 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180614014303_IndexForEFAlias.Designer.cs @@ -0,0 +1,639 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using SharedLibraryCore.Database; +using SharedLibraryCore.Objects; +using System; + +namespace SharedLibraryCore.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20180614014303_IndexForEFAlias")] + partial class IndexForEFAlias + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleVector3Id"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationVector3Id"); + + b.Property("HitLocation"); + + b.Property("HitOriginVector3Id"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleVector3Id"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("When"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleVector3Id"); + + b.HasIndex("HitDestinationVector3Id"); + + b.HasIndex("HitOriginVector3Id"); + + b.HasIndex("LastStrainAngleVector3Id"); + + b.ToTable("EFACSnapshot"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("HitLoc"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("Weapon"); + + b.Property("When"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("ServerId") + .HasColumnName("EFClientStatistics_ServerId"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ClientId", "ServerId"); + + b.ToTable("EFHitLocationCounts"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired(); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("TotalConnectionTime"); + + b.HasKey("ClientId"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId") + .IsUnique(); + + b.ToTable("EFClients"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired(); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("Z"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleVector3Id"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics") + .WithMany("HitLocations") + .HasForeignKey("ClientId", "ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("EFACSnapshotSnapshotId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SharedLibraryCore/Migrations/20180614014303_IndexForEFAlias.cs b/SharedLibraryCore/Migrations/20180614014303_IndexForEFAlias.cs new file mode 100644 index 000000000..8b5d732c8 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180614014303_IndexForEFAlias.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace SharedLibraryCore.Migrations +{ + public partial class IndexForEFAlias : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_EFAlias_IPAddress", + table: "EFAlias", + column: "IPAddress"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_EFAlias_IPAddress", + table: "EFAlias"); + } + } +} diff --git a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs index ea3f44236..5110c610a 100644 --- a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs +++ b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs @@ -312,6 +312,8 @@ namespace SharedLibraryCore.Migrations b.HasKey("AliasId"); + b.HasIndex("IPAddress"); + b.HasIndex("LinkId"); b.ToTable("EFAlias"); @@ -448,8 +450,6 @@ namespace SharedLibraryCore.Migrations b.HasKey("Vector3Id"); - //b.HasIndex("EFACSnapshotSnapshotId"); - b.ToTable("Vector3"); }); diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 936394f28..5d4b5fe5f 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -139,6 +139,13 @@ namespace SharedLibraryCore #else Logger.WriteVerbose(Message.StripColors()); #endif + Manager.GetEventHandler().AddEvent(new GameEvent() + { + Type = GameEvent.EventType.Broadcast, + Data = Message, + Owner = this + }); + await Task.CompletedTask; } diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index d811cd9da..52f502b91 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -59,7 +59,7 @@ namespace SharedLibraryCore.Services Level = hasExistingAlias ? (await context.Clients.Where(c => c.AliasLinkId == existingAlias.LinkId) .OrderByDescending(c => c.Level) - .FirstAsync()).Level : + .FirstOrDefaultAsync())?.Level ?? Player.Permission.User : Player.Permission.User, FirstConnection = DateTime.UtcNow, Connections = 1, @@ -170,11 +170,16 @@ namespace SharedLibraryCore.Services { // get all clients that use the same aliasId var matchingClients = context.Clients - .Where(c => c.CurrentAliasId == client.CurrentAliasId); + .Where(c => c.CurrentAliasId == client.CurrentAliasId) + // make sure we don't select ourselves twice + .Where(c => c.ClientId != entity.ClientId); // update all related clients level - await matchingClients.ForEachAsync(c => c.Level = (client.Level == Player.Permission.Banned) ? - client.Level : entity.Level); + await matchingClients.ForEachAsync(c => + { + c.Level = (client.Level == Player.Permission.Banned) ? client.Level : entity.Level; + + }); } // their alias has been updated and not yet saved diff --git a/WebfrontCore/Controllers/API/EventController.cs b/WebfrontCore/Controllers/API/EventController.cs index de7cc3549..0ff9544c4 100644 --- a/WebfrontCore/Controllers/API/EventController.cs +++ b/WebfrontCore/Controllers/API/EventController.cs @@ -9,13 +9,10 @@ namespace WebfrontCore.Controllers.API { [HttpGet] [Route("event")] - public ActionResult Index() + public ActionResult Index(bool shouldConsume = true) { - var events = Manager.GetEventApi().GetEvents(); - var eventsDto = new List(); - while (events.Count > 0) - eventsDto.Add(events.Dequeue()); - return Json(eventsDto); + var events = Manager.GetEventApi().GetEvents(shouldConsume); + return Json(events); } } } diff --git a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs index ebb729f39..d97bca157 100644 --- a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs +++ b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs @@ -24,7 +24,8 @@ namespace WebfrontCore.ViewComponents Offense = User.Identity.IsAuthenticated && !string.IsNullOrEmpty(p.AutomatedOffense) ? p.AutomatedOffense : p.Offense, Type = p.Type.ToString(), TimePunished = Utilities.GetTimePassed(p.When, false), - TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow), + // show time passed if ban + TimeRemaining = DateTime.UtcNow > p.Expires ? "" : $"{(p.Expires.Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(p.When, true) : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow))}", Sensitive = p.Type == Penalty.PenaltyType.Flag, AutomatedOffense = p.AutomatedOffense }); diff --git a/WebfrontCore/Views/Penalty/_Penalty.cshtml b/WebfrontCore/Views/Penalty/_Penalty.cshtml index 5383cbf4a..7bba5ffd1 100644 --- a/WebfrontCore/Views/Penalty/_Penalty.cshtml +++ b/WebfrontCore/Views/Penalty/_Penalty.cshtml @@ -70,7 +70,7 @@ } else { - @Model.TimeRemaining @loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"] + @Model.TimeRemaining } }