From 17c28f135d223ce881fbe3d8e17c9a1ce6041c0d Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 1 Feb 2020 20:17:26 +0100 Subject: [PATCH 01/23] Started working on adding guilds. Requires client modding. --- InfectedRose | 2 +- Uchu.Core/Database/Models/Character.cs | 2 + Uchu.Core/Database/Models/Guild.cs | 15 + Uchu.Core/Database/Models/GuildInvite.cs | 18 + .../Database/Providers/UchuContextBase.cs | 4 + Uchu.Core/Database/UchuContext.cs | 4 + .../MySql/MySqlContextModelSnapshot.cs | 42 ++ .../Postgres/PostgresContextModelSnapshot.cs | 42 ++ Uchu.StandardScripts/General/Mailbox.cs | 2 +- Uchu.World/Behaviors/AreaOfEffect.cs | 6 +- Uchu.World/Behaviors/BehaviorTree.cs | 7 +- Uchu.World/Behaviors/ExecutionContext.cs | 2 +- Uchu.World/Behaviors/TacArcBehavior.cs | 11 +- Uchu.World/Client/CdClient/MissionParser.cs | 23 - Uchu.World/Client/ZoneInfo.cs | 3 + Uchu.World/Client/ZoneParser.cs | 14 +- .../Commands/CharacterCommandHandler.cs | 4 +- .../Handlers/Commands/GuildCommandHandler.cs | 399 ++++++++++++++++++ Uchu.World/Handlers/SocialHandler.cs | 2 + Uchu.World/MissionSystem/MissionInstance.cs | 6 +- .../Components/Player/Concepts/Inventory.cs | 3 +- .../ReplicaComponents/SkillComponent.cs | 8 +- Uchu.World/Objects/GameObjects/Player.cs | 9 +- Uchu.World/Objects/Object.cs | 2 +- .../Perspective/Filters/ExcludeFilter.cs | 38 ++ .../Packets/Chat/PrivateChatMessagePacket.cs | 56 +++ Uchu.World/Social/GuildGuiState.cs | 9 + Uchu.World/Social/UiHelper.cs | 145 +++++-- Uchu.World/Structs/GuildMember.cs | 13 + Uchu.World/Uchu.World.csproj | 1 + Uchu.sln | 6 + 31 files changed, 820 insertions(+), 78 deletions(-) create mode 100644 Uchu.Core/Database/Models/Guild.cs create mode 100644 Uchu.Core/Database/Models/GuildInvite.cs create mode 100644 Uchu.World/Handlers/Commands/GuildCommandHandler.cs create mode 100644 Uchu.World/Objects/Perspective/Filters/ExcludeFilter.cs create mode 100644 Uchu.World/Packets/Chat/PrivateChatMessagePacket.cs create mode 100644 Uchu.World/Social/GuildGuiState.cs create mode 100644 Uchu.World/Structs/GuildMember.cs diff --git a/InfectedRose b/InfectedRose index a1a3e6b1..727a7640 160000 --- a/InfectedRose +++ b/InfectedRose @@ -1 +1 @@ -Subproject commit a1a3e6b1af06069ddb4b792e24ce4c81eba0a72e +Subproject commit 727a7640be8161fbfa7f83d6676d522206334482 diff --git a/Uchu.Core/Database/Models/Character.cs b/Uchu.Core/Database/Models/Character.cs index 5bfad15a..89de6ff5 100644 --- a/Uchu.Core/Database/Models/Character.cs +++ b/Uchu.Core/Database/Models/Character.cs @@ -179,6 +179,8 @@ public class Character public bool LandingByRocket { get; set; } public int InventorySize { get; set; } + + public long GuildId { get; set; } public ZoneId LaunchedRocketFrom { get; set; } diff --git a/Uchu.Core/Database/Models/Guild.cs b/Uchu.Core/Database/Models/Guild.cs new file mode 100644 index 00000000..05ae9640 --- /dev/null +++ b/Uchu.Core/Database/Models/Guild.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Uchu.Core +{ + public class Guild + { + public long Id { get; set; } + + public string Name { get; set; } + + public long CreatorId { get; set; } + + public List Invites { get; set; } + } +} \ No newline at end of file diff --git a/Uchu.Core/Database/Models/GuildInvite.cs b/Uchu.Core/Database/Models/GuildInvite.cs new file mode 100644 index 00000000..f08b51ab --- /dev/null +++ b/Uchu.Core/Database/Models/GuildInvite.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Uchu.Core +{ + public class GuildInvite + { + public long Id { get; set; } + + public long SenderId { get; set; } + + public long RecipientId { get; set; } + + public long GuildId { get; set; } + + [ForeignKey(nameof(GuildId))] + public Guild Guild { get; set; } + } +} \ No newline at end of file diff --git a/Uchu.Core/Database/Providers/UchuContextBase.cs b/Uchu.Core/Database/Providers/UchuContextBase.cs index 286095a2..1691c3d9 100644 --- a/Uchu.Core/Database/Providers/UchuContextBase.cs +++ b/Uchu.Core/Database/Providers/UchuContextBase.cs @@ -28,6 +28,10 @@ public abstract class UchuContextBase : DbContext, IAsyncDisposable public DbSet Mails { get; set; } + public DbSet Guilds { get; set; } + + public DbSet GuildInvites { get; set; } + public virtual async Task EnsureUpdatedAsync() { await Database.MigrateAsync().ConfigureAwait(false); diff --git a/Uchu.Core/Database/UchuContext.cs b/Uchu.Core/Database/UchuContext.cs index 53dd1355..a14a6ffc 100644 --- a/Uchu.Core/Database/UchuContext.cs +++ b/Uchu.Core/Database/UchuContext.cs @@ -31,6 +31,10 @@ public sealed class UchuContext : IAsyncDisposable, IDisposable public DbSet Mails => ContextBase.Mails; + public DbSet Guilds => ContextBase.Guilds; + + public DbSet GuildInvites => ContextBase.GuildInvites; + public UchuContext() { var config = UchuContextBase.Config; diff --git a/Uchu.Core/Migrations/MySql/MySqlContextModelSnapshot.cs b/Uchu.Core/Migrations/MySql/MySqlContextModelSnapshot.cs index ab354458..78d5f2b7 100644 --- a/Uchu.Core/Migrations/MySql/MySqlContextModelSnapshot.cs +++ b/Uchu.Core/Migrations/MySql/MySqlContextModelSnapshot.cs @@ -44,6 +44,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FreeToPlay"); + b.Property("GuildId"); + b.Property("HairColor"); b.Property("HairStyle"); @@ -214,6 +216,38 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Friends"); }); + modelBuilder.Entity("Uchu.Core.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RecipientId"); + + b.Property("SenderId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildInvites"); + }); + modelBuilder.Entity("Uchu.Core.InventoryItem", b => { b.Property("InventoryItemId") @@ -425,6 +459,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.HasOne("Uchu.Core.Guild", "Guild") + .WithMany("Invites") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("Uchu.Core.InventoryItem", b => { b.HasOne("Uchu.Core.Character", "Character") diff --git a/Uchu.Core/Migrations/Postgres/PostgresContextModelSnapshot.cs b/Uchu.Core/Migrations/Postgres/PostgresContextModelSnapshot.cs index 9ed29280..467918f2 100644 --- a/Uchu.Core/Migrations/Postgres/PostgresContextModelSnapshot.cs +++ b/Uchu.Core/Migrations/Postgres/PostgresContextModelSnapshot.cs @@ -46,6 +46,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FreeToPlay"); + b.Property("GuildId"); + b.Property("HairColor"); b.Property("HairStyle"); @@ -217,6 +219,38 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Friends"); }); + modelBuilder.Entity("Uchu.Core.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RecipientId"); + + b.Property("SenderId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildInvites"); + }); + modelBuilder.Entity("Uchu.Core.InventoryItem", b => { b.Property("InventoryItemId") @@ -428,6 +462,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.HasOne("Uchu.Core.Guild", "Guild") + .WithMany("Invites") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("Uchu.Core.InventoryItem", b => { b.HasOne("Uchu.Core.Character", "Character") diff --git a/Uchu.StandardScripts/General/Mailbox.cs b/Uchu.StandardScripts/General/Mailbox.cs index 10a38a16..81af87e2 100644 --- a/Uchu.StandardScripts/General/Mailbox.cs +++ b/Uchu.StandardScripts/General/Mailbox.cs @@ -13,7 +13,7 @@ public override Task LoadAsync() { Listen(gameObject.OnInteract, async player => { - await UiHelper.OpenMailboxAsync(player); + await player.OpenMailboxGuiAsync(); }); } diff --git a/Uchu.World/Behaviors/AreaOfEffect.cs b/Uchu.World/Behaviors/AreaOfEffect.cs index a2959cfe..527099de 100644 --- a/Uchu.World/Behaviors/AreaOfEffect.cs +++ b/Uchu.World/Behaviors/AreaOfEffect.cs @@ -19,9 +19,7 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc var length = context.Reader.Read(); - ((Player) context.Associate).SendChatMessage($"AREA LENGTH: {length}"); - - if (length > 10) length = 10; + if (length > 10) length = 10; // TODO: Fix context.Writer.Write(length); @@ -35,8 +33,6 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Associate.Zone.TryGetGameObject((long) id, out var target); - ((Player) context.Associate)?.SendChatMessage($"AREA: {id} -> {target}"); - targets[i] = target; } diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 142cdf35..33986e46 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -149,9 +149,6 @@ public async Task BuildAsync() public async Task ExecuteAsync(GameObject associate, BitReader reader, BitWriter writer, SkillCastType castType = SkillCastType.OnEquip, GameObject target = default, bool explicitTarget = false) { - if (!explicitTarget) - target = associate; - var context = new ExecutionContext(associate, reader, writer); if (RootBehaviors.TryGetValue(SkillCastType.Default, out var defaultList)) @@ -160,7 +157,7 @@ public async Task ExecuteAsync(GameObject associate, BitReader { context.Root = root; - var branchContext = new ExecutionBranchContext(target); + var branchContext = new ExecutionBranchContext(associate); await root.ExecuteAsync(context, branchContext); } @@ -172,7 +169,7 @@ public async Task ExecuteAsync(GameObject associate, BitReader { context.Root = root; - var branchContext = new ExecutionBranchContext(target); + var branchContext = new ExecutionBranchContext(associate); await root.ExecuteAsync(context, branchContext); } diff --git a/Uchu.World/Behaviors/ExecutionContext.cs b/Uchu.World/Behaviors/ExecutionContext.cs index d0d2dbf3..075dc6be 100644 --- a/Uchu.World/Behaviors/ExecutionContext.cs +++ b/Uchu.World/Behaviors/ExecutionContext.cs @@ -16,7 +16,7 @@ public class ExecutionContext public BitReader Reader { get; set; } public BitWriter Writer { get; set; } - + public uint SkillId { get; set; } public Dictionary BehaviorHandles { get; } = new Dictionary(); diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 817ea7bc..326be3d0 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Uchu.Core; @@ -43,11 +44,13 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Writer.WriteBit(checkEnvironment); } - var targets = new GameObject[context.Reader.Read()]; + var specifiedTargets = context.Reader.Read(); - context.Writer.Write((uint) targets.Length); + context.Writer.Write(specifiedTargets); + + var targets = new List(); - for (var i = 0; i < targets.Length; i++) + for (var i = 0; i < specifiedTargets; i++) { var targetId = context.Reader.Read(); @@ -60,7 +63,7 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc continue; } - targets[i] = target; + targets.Add(target); } foreach (var target in targets) diff --git a/Uchu.World/Client/CdClient/MissionParser.cs b/Uchu.World/Client/CdClient/MissionParser.cs index c6ae593e..3abb8b8a 100644 --- a/Uchu.World/Client/CdClient/MissionParser.cs +++ b/Uchu.World/Client/CdClient/MissionParser.cs @@ -25,8 +25,6 @@ private static bool Check(bool a, bool b, Mode mode) private static bool IsCompleted(string str, Mission[] completed) { - Logger.Information($"Complete: {str} ANY {string.Join(' ', completed.Select(s => s.Id))}"); - if (string.IsNullOrWhiteSpace(str)) return true; if (str.Contains(':')) @@ -49,8 +47,6 @@ private static bool IsCompleted(string str, Mission[] completed) return chrTask.Values.Count >= task.TargetValue; } - Logger.Debug($"Required mission {str}"); - var id = int.Parse(str); return completed.Any(c => c.MissionId == id); @@ -129,25 +125,6 @@ public static bool CheckPrerequiredMissions(string missions, Mission[] completed return res; } - public static async Task AllTasksCompletedAsync(Mission mission) - { - await using var ctx = new CdClientContext(); - - // - // Get all tasks this mission have to have completed to be handed in. - // - - var tasks = ctx.MissionTasksTable.Where(m => m.Id == mission.MissionId); - - // - // Check tasks count to their required values. - // - - return (await tasks.ToArrayAsync()).All(t => - mission.Tasks.Find(t2 => t2.TaskId == t.Uid).ValueArray().Length >= t.TargetValue - ); - } - public static Lot[] GetTargets(MissionTasks missionTask) { var targets = new List(); diff --git a/Uchu.World/Client/ZoneInfo.cs b/Uchu.World/Client/ZoneInfo.cs index b81be4ed..33817f23 100644 --- a/Uchu.World/Client/ZoneInfo.cs +++ b/Uchu.World/Client/ZoneInfo.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using InfectedRose.Luz; using InfectedRose.Lvl; +using InfectedRose.Terrain; using InfectedRose.Triggers; namespace Uchu.World.Client @@ -9,6 +10,8 @@ public class ZoneInfo { public LuzFile LuzFile { get; set; } + public TerrainFile TerrainFile { get; set; } + public List Triggers { get; set; } public List LvlFiles { get; set; } diff --git a/Uchu.World/Client/ZoneParser.cs b/Uchu.World/Client/ZoneParser.cs index 96870d0c..81f1526b 100644 --- a/Uchu.World/Client/ZoneParser.cs +++ b/Uchu.World/Client/ZoneParser.cs @@ -6,6 +6,7 @@ using System.Xml.Serialization; using InfectedRose.Luz; using InfectedRose.Lvl; +using InfectedRose.Terrain; using InfectedRose.Triggers; using RakDotNet.IO; using Uchu.Core; @@ -59,7 +60,7 @@ public async Task LoadZoneDataAsync(int seek) { await using var sceneStream = _resources.GetStream(Path.Combine(path, scene.FileName)); - var sceneReader = new BitReader(sceneStream); + using var sceneReader = new BitReader(sceneStream); var lvl = new LvlFile(); @@ -74,6 +75,14 @@ public async Task LoadZoneDataAsync(int seek) template.ObjectId |= 70368744177664; } } + + var terrainStream = _resources.GetStream(Path.Combine(path, luz.TerrainFileName)); + + using var terrainReader = new BitReader(terrainStream); + + var terrain = new TerrainFile(); + + terrain.Deserialize(terrainReader); Logger.Information($"Parsed: {(ZoneId) luz.WorldId}"); @@ -81,7 +90,8 @@ public async Task LoadZoneDataAsync(int seek) { LuzFile = luz, LvlFiles = lvlFiles, - Triggers = triggers.ToList() + Triggers = triggers.ToList(), + TerrainFile = terrain }; } catch (Exception e) diff --git a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs index 3dc36a33..91d98ffb 100644 --- a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs @@ -771,11 +771,11 @@ public async Task MailBox(string[] arguments, Player player) if (state) { - await UiHelper.OpenMailboxAsync(player); + await player.OpenMailboxGuiAsync(); } else { - await UiHelper.CloneMailboxAsync(player); + await player.CloseMailboxGuiAsync(); } return $"Set mailbox state to: {state}"; diff --git a/Uchu.World/Handlers/Commands/GuildCommandHandler.cs b/Uchu.World/Handlers/Commands/GuildCommandHandler.cs new file mode 100644 index 00000000..0cf6ca8a --- /dev/null +++ b/Uchu.World/Handlers/Commands/GuildCommandHandler.cs @@ -0,0 +1,399 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Uchu.Core; +using Uchu.World.Social; + +namespace Uchu.World.Handlers.Commands +{ + public class GuildCommandHandler : HandlerGroup + { + [CommandHandler(Signature = "guild", Help = "Guild commands", GameMasterLevel = GameMasterLevel.Player)] + public async Task Guild(string[] arguments, Player player) + { + if (player.GuildGuiState == GuildGuiState.Suppress) + { + player.GuildGuiState = GuildGuiState.Creating; + + return "Executed guild command."; + } + + if (arguments.Length == default) + { + await SendGuildInformationAsync(player); + + return "Executed guild command."; + } + + var args = arguments.ToList(); + + var action = args[0]; + + args.RemoveAt(0); + + await using var ctx = new UchuContext(); + + var character = await ctx.Characters.FirstAsync(c => c.CharacterId == player.ObjectId); + + Guild guild; + + switch (action.ToLower()) + { + case "new": + if (character.GuildId != 0) + { + player.CentralNoticeGui("You are already a member of a guild!"); + + break; + } + + await player.GuildCreateGuiAsync(true); + + await player.SingleArgumentGuiAsync( + "SetGuildCreateHeader", + "text", + "Create Guild" + ); + + await player.SingleArgumentGuiAsync( + "SetGuildCreateDescription", + "text", + "Guilds are an amazing tool for creating a community!" + ); + + await player.SingleArgumentGuiAsync( + "SetGuildCreateName", + "text", + "Select a name and click the button to create your guild." + ); + + await player.MessageGuiAsync("EnableGuildCreateInput"); + + break; + case "create": + if (player.GuildInviteName != default) return "Suppressed"; + + if (character.GuildId != 0) + { + player.CentralNoticeGui("You are already a member of a guild!"); + + break; + } + + var name = string.Join(" ", args); + + if (string.IsNullOrWhiteSpace(name)) + { + player.CentralNoticeGui("Your guild needs a name!"); + + break; + } + + if (await ctx.Guilds.AnyAsync(g => g.Name == name)) + { + player.CentralNoticeGui("This guild name is taken!"); + + break; + } + + player.CentralNoticeGui($"You created your guild \"{name}\"!"); + + var id = IdUtilities.GenerateObjectId(); + + guild = new Guild + { + Id = id, + CreatorId = character.CharacterId, + Name = name + }; + + character.GuildId = id; + + await ctx.Guilds.AddAsync(guild); + + await ctx.SaveChangesAsync(); + + await SendGuildInformationAsync(player); + + break; + case "leave": + if (character.GuildId != 0) + { + player.CentralNoticeGui("You are not a member of a guild!"); + + break; + } + + guild = await ctx.Guilds.FirstAsync(g => g.Id == character.GuildId); + + if (guild.CreatorId == character.CharacterId) + { + await DismantleGuildAsync(player); + } + + character.GuildId = 0; + + await ctx.SaveChangesAsync(); + + player.CentralNoticeGui("You left your guild!"); + break; + case "invite": + if (character.GuildId == 0) + { + player.CentralNoticeGui("You are not a member of a guild!"); + + break; + } + + var playerName = string.Join(" ", args); + + var invited = await ctx.Characters.FirstOrDefaultAsync(c => c.Name == playerName); + + if (invited == null) + { + player.CentralNoticeGui($"No player named \"{playerName}\" found, please try again."); + + break; + } + + if (invited.GuildId != 0) + { + player.CentralNoticeGui($"{playerName} is already in a guild!"); + + break; + } + + guild = await ctx.Guilds.Include(g => g.Invites).FirstAsync( + g => g.Id == character.GuildId + ); + + if (guild.Invites.Any(i => i.RecipientId == invited.CharacterId)) + { + player.CentralNoticeGui($"There is already a pending invite to {playerName}!"); + + break; + } + + var invite = new GuildInvite + { + GuildId = guild.Id, + RecipientId = invited.CharacterId, + SenderId = character.CharacterId + }; + + await ctx.GuildInvites.AddAsync(invite); + + await ctx.SaveChangesAsync(); + + var invitedPlayer = player.Zone.Players.FirstOrDefault(p => p.ObjectId == invited.CharacterId); + + if (invitedPlayer != default) + { + await DisplayGuildInviteAsync(invitedPlayer); + } + + player.CentralNoticeGui($"Send a guild invite to {playerName}!"); + + break; + case "invites": + await DisplayGuildInviteAsync(player); + + break; + case "accept": + if (player.GuildInviteName == default) return "Suppressed"; + + player.GuildGuiState = GuildGuiState.Suppress; + + guild = await ctx.Guilds.Include(g => g.Invites).FirstOrDefaultAsync( + g => g.Name == player.GuildInviteName + ); + + if (guild == default) + { + player.CentralNoticeGui($"No guild named \"{player.GuildInviteName}\" found, please try again."); + + break; + } + + var guildInvite = guild.Invites.FirstOrDefault(i => i.RecipientId == player.ObjectId); + + if (guildInvite == default) + { + player.CentralNoticeGui($"You have no pending invite to {guild}!"); + + break; + } + + guild.Invites.Remove(guildInvite); + + character.GuildId = guild.Id; + + player.GuildInviteName = default; + + await ctx.SaveChangesAsync(); + + player.CentralNoticeGui($"You accepted the invite to {guild.Name}!"); + + break; + case "decline": + if (player.GuildInviteName == default) return "Suppressed"; + + player.GuildGuiState = GuildGuiState.Suppress; + + guild = await ctx.Guilds.Include(g => g.Invites).FirstOrDefaultAsync( + g => g.Name == player.GuildInviteName + ); + + if (guild == default) + { + player.CentralNoticeGui($"No guild named \"{player.GuildInviteName}\" found, please try again."); + + break; + } + + var declineInvite = guild.Invites.FirstOrDefault(i => i.RecipientId == player.ObjectId); + + if (declineInvite == default) + { + player.CentralNoticeGui($"You have no pending invite to {guild}!"); + + break; + } + + guild.Invites.Remove(declineInvite); + + await ctx.SaveChangesAsync(); + + player.GuildInviteName = default; + + player.CentralNoticeGui($"You declined the invite to {guild.Name}!"); + + break; + } + + return "Executed guild command."; + } + + public static async Task SendGuildInformationAsync(Player player) + { + await using var ctx = new UchuContext(); + + var character = await ctx.Characters.FirstAsync(c => c.CharacterId == player.ObjectId); + + var guild = await ctx.Guilds.FirstOrDefaultAsync(g => g.Id == character.GuildId); + + if (guild == default) + { + player.CentralNoticeGui("You are not a member of a guild!"); + + return; + } + + await UiHelper.SetGuildNameAsync(player, guild.Name); + + await player.MessageGuiAsync("ClearGuildMembers"); + + var members = await ctx.Characters.Where(c => c.GuildId == guild.Id).ToArrayAsync(); + + var index = 0; + + foreach (var member in members) + { + var memberPlayer = player.Zone.Players.FirstOrDefault(p => p.ObjectId == member.CharacterId); + + await UiHelper.AddGuildMemberAsync(player, index++, new GuildMember + { + Name = member.Name, + Online = memberPlayer != default, + Rank = guild.CreatorId == member.CharacterId ? "Owner" : "Member", + Zone = ((ZoneId) member.LastZone).ToString() + }); + } + + await player.GuildMenuGuiAsync(true); + } + + public static async Task DismantleGuildAsync(Player player) + { + await using var ctx = new UchuContext(); + + var character = await ctx.Characters.FirstAsync(c => c.CharacterId == player.ObjectId); + + var guild = await ctx.Guilds.FirstAsync(g => g.Id == character.GuildId); + + var members = await ctx.Characters.Where(c => c.GuildId == guild.Id).ToArrayAsync(); + + foreach (var member in members) + { + member.GuildId = 0; + + var memberPlayer = player.Zone.Players.FirstOrDefault(p => p.ObjectId == member.CharacterId); + + memberPlayer?.CentralNoticeGui($"{guild.Name} was dismantled, you are no longer a member of a guild."); + } + + ctx.Guilds.Remove(guild); + + await ctx.SaveChangesAsync(); + } + + public static async Task DisplayGuildInviteAsync(Player player, GuildInvite invite = default) + { + await using var ctx = new UchuContext(); + + if (invite == default) + { + invite = await ctx.GuildInvites.FirstOrDefaultAsync( + g => g.RecipientId == player.ObjectId + ); + } + else + { + invite = await ctx.GuildInvites.FirstOrDefaultAsync( + g => g.Id == invite.Id + ); + } + + if (invite == default) return; + + var guild = await ctx.Guilds.FirstOrDefaultAsync(g => g.Id == invite.GuildId); + + if (guild == default) + { + ctx.GuildInvites.Remove(invite); + + await ctx.SaveChangesAsync(); + + return; + } + + await player.SingleArgumentGuiAsync( + "SetGuildCreateHeader", + "text", + "Guild Invite" + ); + + await player.SingleArgumentGuiAsync( + "SetGuildCreateDescription", + "text", + $"You are invited to join {guild.Name}!" + ); + + await player.SingleArgumentGuiAsync( + "SetGuildCreateName", + "text", + "Click the button to accept the invite" + ); + + await player.MessageGuiAsync("DisableGuildCreateInput"); + + await player.GuildCreateGuiAsync(true); + + player.GuildGuiState = GuildGuiState.Invite; + + player.GuildInviteName = guild.Name; + + await ctx.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/Uchu.World/Handlers/SocialHandler.cs b/Uchu.World/Handlers/SocialHandler.cs index a3b33b8f..e3c46818 100644 --- a/Uchu.World/Handlers/SocialHandler.cs +++ b/Uchu.World/Handlers/SocialHandler.cs @@ -32,6 +32,8 @@ public async Task ParseChatMessageHandler(ParseChatMessage message, Player playe var character = await ctx.Characters.Include(c => c.User) .FirstAsync(c => c.CharacterId == player.ObjectId); + + Console.WriteLine($"Message: {message.Message}"); var response = await Server.HandleCommandAsync( message.Message, diff --git a/Uchu.World/MissionSystem/MissionInstance.cs b/Uchu.World/MissionSystem/MissionInstance.cs index eff3eb54..214f255a 100644 --- a/Uchu.World/MissionSystem/MissionInstance.cs +++ b/Uchu.World/MissionSystem/MissionInstance.cs @@ -228,8 +228,8 @@ public async Task SendRewardsAsync(int rewardItem) m => m.Id == MissionId ); - var currency = repeat ? clientMission.Rewardcurrency ?? 0 : clientMission.Rewardcurrencyrepeatable ?? 0; - var score = repeat ? clientMission.LegoScore ?? 0 : 0; + var currency = !repeat ? clientMission.Rewardcurrency ?? 0 : clientMission.Rewardcurrencyrepeatable ?? 0; + var score = !repeat ? clientMission.LegoScore ?? 0 : 0; if (clientMission.IsMission ?? true) { @@ -275,7 +275,7 @@ public async Task SendRewardsAsync(int rewardItem) var inventory = Player.GetComponent(); - inventory[InventoryType.Items].Size += repeat ? clientMission.Rewardmaxinventory ?? 0 : 0; + inventory[InventoryType.Items].Size += !repeat ? clientMission.Rewardmaxinventory ?? 0 : 0; var rewards = new (Lot, int)[] { diff --git a/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs b/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs index e6bf0aac..abb025fd 100644 --- a/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs +++ b/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs @@ -16,7 +16,6 @@ public class Inventory private int _size; - // TODO: Network & Store in DB public int Size { get @@ -79,7 +78,7 @@ internal Inventory(InventoryType inventoryType, InventoryManagerComponent manage i => Item.Instantiate(i.InventoryItemId, this) ).Where(item => !ReferenceEquals(item, default)).ToList(); - _size = inventoryType != InventoryType.Items ? 1000 : playerCharacter.InventorySize; + Size = inventoryType != InventoryType.Items ? 1000 : playerCharacter.InventorySize; foreach (var item in _items) { diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index f9199d51..c666a21a 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -158,7 +158,13 @@ public async Task StartUserSkillAsync(StartSkillMessage message) await using var writeStream = new MemoryStream(); using var writer = new BitWriter(writeStream); - var context = await tree.ExecuteAsync(GameObject, reader, writer, SkillCastType.OnUse); + var context = await tree.ExecuteAsync( + GameObject, + reader, + writer, + SkillCastType.OnUse, + message.OptionalTarget + ); _handledSkills[message.SkillHandle] = context; diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index ac7c47e5..7f2d709d 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -71,6 +71,10 @@ private Player() public PlayerChatChannel ChatChannel { get; set; } + public GuildGuiState GuildGuiState { get; set; } + + public string GuildInviteName { get; set; } + public override string Name { get => ObjectName; @@ -340,7 +344,7 @@ public void Teleport(Vector3 position) }); } - public void SendChatMessage(string message, PlayerChatChannel channel = PlayerChatChannel.Debug, Player author = null) + public void SendChatMessage(string message, PlayerChatChannel channel = PlayerChatChannel.Debug, Player author = null, ChatChannel chatChannel = World.ChatChannel.Public) { if (channel > ChatChannel) return; @@ -348,7 +352,8 @@ public void SendChatMessage(string message, PlayerChatChannel channel = PlayerCh { Message = $"{message}\0", Sender = author, - IsMythran = author?.GameMasterLevel > 0 + IsMythran = author?.GameMasterLevel > 0, + Channel = chatChannel }); } diff --git a/Uchu.World/Objects/Object.cs b/Uchu.World/Objects/Object.cs index 005aeb3c..1fec9dad 100644 --- a/Uchu.World/Objects/Object.cs +++ b/Uchu.World/Objects/Object.cs @@ -69,7 +69,7 @@ public static void Destroy(Object obj) protected static void Update(Object obj) { - obj.OnTick.Invoke(); + obj?.OnTick.Invoke(); } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Perspective/Filters/ExcludeFilter.cs b/Uchu.World/Objects/Perspective/Filters/ExcludeFilter.cs new file mode 100644 index 00000000..5930e439 --- /dev/null +++ b/Uchu.World/Objects/Perspective/Filters/ExcludeFilter.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Uchu.World.Filters +{ + public class ExcludeFilter : IPerspectiveFilter + { + private List _excluded; + + public GameObject[] Excluded => _excluded.ToArray(); + + public void Initialize(Player player) + { + _excluded = new List(); + } + + public Task Tick() + { + for (var index = 0; index < _excluded.Count; index++) + { + var gameObject = _excluded[index]; + + if (!gameObject.Alive) _excluded.Remove(gameObject); + } + + return Task.CompletedTask; + } + + public bool View(GameObject gameObject) + { + return !_excluded.Contains(gameObject); + } + + public void Exclude(GameObject gameObject) => _excluded.Add(gameObject); + + public void Include(GameObject gameObject) => _excluded.Remove(gameObject); + } +} \ No newline at end of file diff --git a/Uchu.World/Packets/Chat/PrivateChatMessagePacket.cs b/Uchu.World/Packets/Chat/PrivateChatMessagePacket.cs new file mode 100644 index 00000000..e2fef123 --- /dev/null +++ b/Uchu.World/Packets/Chat/PrivateChatMessagePacket.cs @@ -0,0 +1,56 @@ +using RakDotNet.IO; +using Uchu.Core; + +namespace Uchu.World +{ + public class PrivateChatMessagePacket : Packet + { + public override RemoteConnectionType RemoteConnectionType => RemoteConnectionType.Chat; + + public override uint PacketId => 0x2; + + public ulong UnknownObjectId { get; set; } + + public ChatChannel Channel { get; set; } = ChatChannel.Public; + + public Player Sender { get; set; } + + public bool IsMythran { get; set; } + + public string Message { get; set; } + + public override void SerializePacket(BitWriter writer) + { + writer.Write(UnknownObjectId); + + writer.Write((byte) Channel); + + writer.Write((uint) (Message.Length * 2)); + + if (Sender != null) + { + writer.WriteString(Sender.Name, wide: true); + + writer.Write((ulong) Sender.ObjectId); + } + else + { + writer.WriteString("", wide: true); + + writer.Write(default); + } + + writer.Write(0); + + writer.Write((byte) (IsMythran ? 1 : 0)); + + writer.WriteString(Sender != null ? Sender.Name : "", wide: true); + + writer.Write((byte) (IsMythran ? 1 : 0)); + + writer.Write(0); + + writer.WriteString(Message, Message.Length, true); + } + } +} \ No newline at end of file diff --git a/Uchu.World/Social/GuildGuiState.cs b/Uchu.World/Social/GuildGuiState.cs new file mode 100644 index 00000000..d50690f2 --- /dev/null +++ b/Uchu.World/Social/GuildGuiState.cs @@ -0,0 +1,9 @@ +namespace Uchu.World.Social +{ + public enum GuildGuiState + { + Creating, + Invite, + Suppress + } +} \ No newline at end of file diff --git a/Uchu.World/Social/UiHelper.cs b/Uchu.World/Social/UiHelper.cs index f5fac118..b66c0236 100644 --- a/Uchu.World/Social/UiHelper.cs +++ b/Uchu.World/Social/UiHelper.cs @@ -6,95 +6,113 @@ namespace Uchu.World.Social { public static class UiHelper { - public static async Task OpenMailboxAsync(Player player) + + public static async Task AnnouncementAsync(Player player, string title, string message) { await using var stream = new MemoryStream(); using var writer = new BitWriter(stream); - + writer.Write((byte) Amf3Type.Array); writer.Write(1); - Amf3Helper.WriteText(writer, "state"); + Amf3Helper.WriteText(writer, "message"); + + writer.Write((byte) Amf3Type.String); + Amf3Helper.WriteText(writer, message); + Amf3Helper.WriteText(writer, "title"); writer.Write((byte) Amf3Type.String); - Amf3Helper.WriteText(writer, "Mail"); + Amf3Helper.WriteText(writer, title); + Amf3Helper.WriteText(writer, "visible"); + writer.Write((byte) Amf3Type.True); writer.Write((byte) Amf3Type.Null); player.Message(new UiMessageServerToSingleClientMessage { Associate = player, Content = stream.ToArray(), - MessageName = "pushGameState" + MessageName = "ToggleAnnounce" }); } - public static async Task CloneMailboxAsync(Player player) + public static async Task StateAsync(Player player, string state) { await using var stream = new MemoryStream(); using var writer = new BitWriter(stream); - + writer.Write((byte) Amf3Type.Array); writer.Write(1); - Amf3Helper.WriteText(writer, "visible"); + Amf3Helper.WriteText(writer, "state"); - writer.Write((byte) Amf3Type.False); - writer.Write((byte) Amf3Type.Null); + writer.Write((byte) Amf3Type.String); + Amf3Helper.WriteText(writer, state); + writer.Write((byte) Amf3Type.Null); + player.Message(new UiMessageServerToSingleClientMessage { Associate = player, Content = stream.ToArray(), - MessageName = "ToggleMail" + MessageName = "pushGameState" }); } - public static async Task AnnouncementAsync(Player player, string title, string message) + public static async Task AddGuildMemberAsync(Player player, int index, GuildMember member) { await using var stream = new MemoryStream(); using var writer = new BitWriter(stream); writer.Write((byte) Amf3Type.Array); writer.Write(1); - Amf3Helper.WriteText(writer, "message"); + Amf3Helper.WriteText(writer, "name"); writer.Write((byte) Amf3Type.String); - Amf3Helper.WriteText(writer, message); - Amf3Helper.WriteText(writer, "title"); + Amf3Helper.WriteText(writer, member.Name); + Amf3Helper.WriteText(writer, "zone"); writer.Write((byte) Amf3Type.String); - Amf3Helper.WriteText(writer, title); - Amf3Helper.WriteText(writer, "visible"); + Amf3Helper.WriteText(writer, member.Zone); + Amf3Helper.WriteText(writer, "rank"); + + writer.Write((byte) Amf3Type.String); + Amf3Helper.WriteText(writer, member.Rank); + Amf3Helper.WriteText(writer, "online"); + + writer.Write((byte) (member.Online ? Amf3Type.True : Amf3Type.False)); + Amf3Helper.WriteText(writer, "index"); + + writer.Write((byte) Amf3Type.Integer); + Amf3Helper.WriteNumber2(writer, (uint) index); - writer.Write((byte) Amf3Type.True); writer.Write((byte) Amf3Type.Null); player.Message(new UiMessageServerToSingleClientMessage { Associate = player, Content = stream.ToArray(), - MessageName = "ToggleAnnounce" + MessageName = "AddGuildMember" }); } - public static async Task StateAsync(Player player, string state) + public static async Task SetGuildNameAsync(Player player, string name) { await using var stream = new MemoryStream(); using var writer = new BitWriter(stream); writer.Write((byte) Amf3Type.Array); writer.Write(1); - Amf3Helper.WriteText(writer, "state"); - + Amf3Helper.WriteText(writer, "guildName"); + writer.Write((byte) Amf3Type.String); - Amf3Helper.WriteText(writer, state); + Amf3Helper.WriteText(writer, name); writer.Write((byte) Amf3Type.Null); - + player.Message(new UiMessageServerToSingleClientMessage { Associate = player, Content = stream.ToArray(), - MessageName = "pushGameState" + MessageName = "SetGuildName" }); } @@ -117,5 +135,82 @@ public static async Task ToggleAsync(Player player, string name, bool value) MessageName = name }); } + + public static async Task SingleArgumentGuiAsync(this Player @this, string name, string argument, string value) + { + await using var stream = new MemoryStream(); + using var writer = new BitWriter(stream); + + writer.Write((byte) Amf3Type.Array); + writer.Write(1); + Amf3Helper.WriteText(writer, argument); + + writer.Write((byte) Amf3Type.String); + Amf3Helper.WriteText(writer, value); + + writer.Write((byte) Amf3Type.Null); + + @this.Message(new UiMessageServerToSingleClientMessage + { + Associate = @this, + Content = stream.ToArray(), + MessageName = name + }); + } + + public static async Task MessageGuiAsync(this Player @this, string name) + { + await using var stream = new MemoryStream(); + using var writer = new BitWriter(stream); + + writer.Write((byte) Amf3Type.Null); + + @this.Message(new UiMessageServerToSingleClientMessage + { + Associate = @this, + Content = stream.ToArray(), + MessageName = name + }); + } + + public static void CentralNoticeGui(this Player @this, string text, int id = 0) + { + @this.Message(new NotifyClientFailedPreconditionMessage + { + Associate = @this, + Id = id, + Reason = text + }); + } + + public static Task CloseMailboxGuiAsync(this Player @this) => ToggleAsync(@this, "ToggleMail", false); + + public static Task CountdownGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleFlashingText", state); + + public static Task BackpackGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleBackpack", state); + + public static Task GuildCreateGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleGuildCreate", state); + + public static Task GuildMenuGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleGuildUI", state); + + public static Task PassportGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "TogglePassport", state); + + public static Task HelpGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleHelpMenu", state); + + public static Task NewsGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleNews", state); + + public static Task FriendListGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleFriendsList", state); + + public static Task RespawnGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleRespawnDialog", state); + + public static Task PerformanceWarningGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "TogglePerformanceWarning", state); + + public static Task SpeedChatGuiAsync(this Player @this, bool state) => ToggleAsync(@this, "ToggleSC", state); + + public static Task OpenGuildChatAsync(this Player @this, bool state) => ToggleAsync(@this, "PlayerInGuild", state); + + public static Task CloseGuildChatAsync(this Player @this, bool state) => ToggleAsync(@this, "PlayerLeftGuild", state); + + public static Task OpenMailboxGuiAsync(this Player @this) => StateAsync(@this, "Mail"); } } \ No newline at end of file diff --git a/Uchu.World/Structs/GuildMember.cs b/Uchu.World/Structs/GuildMember.cs new file mode 100644 index 00000000..ab030b7d --- /dev/null +++ b/Uchu.World/Structs/GuildMember.cs @@ -0,0 +1,13 @@ +namespace Uchu.World +{ + public struct GuildMember + { + public string Name { get; set; } + + public bool Online { get; set; } + + public string Zone { get; set; } + + public string Rank { get; set; } + } +} \ No newline at end of file diff --git a/Uchu.World/Uchu.World.csproj b/Uchu.World/Uchu.World.csproj index 5c43ed38..1bd62f8e 100644 --- a/Uchu.World/Uchu.World.csproj +++ b/Uchu.World/Uchu.World.csproj @@ -9,6 +9,7 @@ + diff --git a/Uchu.sln b/Uchu.sln index 21905a39..115c3b78 100644 --- a/Uchu.sln +++ b/Uchu.sln @@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Utilities", "I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Uchu.Python", "Uchu.Python\Uchu.Python.csproj", "{866D83D6-985A-4473-AF62-78C2716408A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Terrain", "InfectedRose\InfectedRose.Terrain\InfectedRose.Terrain.csproj", "{1C4F4C85-C10A-4AEC-B3D1-9B46DE566527}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +93,10 @@ Global {866D83D6-985A-4473-AF62-78C2716408A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {866D83D6-985A-4473-AF62-78C2716408A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {866D83D6-985A-4473-AF62-78C2716408A9}.Release|Any CPU.Build.0 = Release|Any CPU + {1C4F4C85-C10A-4AEC-B3D1-9B46DE566527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C4F4C85-C10A-4AEC-B3D1-9B46DE566527}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C4F4C85-C10A-4AEC-B3D1-9B46DE566527}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C4F4C85-C10A-4AEC-B3D1-9B46DE566527}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 From c982bd5eb743feeb537dae397f0095c783579fcd Mon Sep 17 00:00:00 2001 From: wincent Date: Mon, 3 Feb 2020 22:13:09 +0100 Subject: [PATCH 02/23] Started working on enemy skills/behavior execution. --- Uchu.World/Behaviors/AndBehavior.cs | 8 ++ Uchu.World/Behaviors/BehaviorBase.cs | 5 ++ Uchu.World/Behaviors/BehaviorTree.cs | 35 +++++++++ Uchu.World/Behaviors/ExecutionContext.cs | 2 - .../Behaviors/NpcCombatSkillBehavior.cs | 34 +++++++++ Uchu.World/Behaviors/NpcExecutionContext.cs | 15 ++++ Uchu.World/Behaviors/TacArcBehavior.cs | 74 +++++++++++++++++++ .../BaseCombatAIComponent.cs | 9 +++ 8 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 Uchu.World/Behaviors/NpcCombatSkillBehavior.cs create mode 100644 Uchu.World/Behaviors/NpcExecutionContext.cs diff --git a/Uchu.World/Behaviors/AndBehavior.cs b/Uchu.World/Behaviors/AndBehavior.cs index 0b4bdb40..88fbbf5c 100644 --- a/Uchu.World/Behaviors/AndBehavior.cs +++ b/Uchu.World/Behaviors/AndBehavior.cs @@ -29,5 +29,13 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc await behavior.ExecuteAsync(context, branchContext); } } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + foreach (var behavior in Behaviors) + { + await behavior.CalculateAsync(context, branchContext); + } + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/BehaviorBase.cs b/Uchu.World/Behaviors/BehaviorBase.cs index 91c54429..10ac9856 100644 --- a/Uchu.World/Behaviors/BehaviorBase.cs +++ b/Uchu.World/Behaviors/BehaviorBase.cs @@ -55,6 +55,11 @@ public virtual async Task ExecuteAsync(ExecutionContext context, ExecutionBranch }); } + public virtual Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + return Task.CompletedTask; + } + public virtual Task SyncAsync(ExecutionContext context, ExecutionBranchContext branchContext) { return Task.CompletedTask; diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 33986e46..2312747b 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -146,7 +146,42 @@ public async Task BuildAsync() CastType = b.castType }).ToArray(); } + + /// + /// Calculate a server preformed skill + /// + /// Executioner + /// Data to be sent to clients + /// Type of skill + /// Context + public async Task CalculateAsync(GameObject associate, BitWriter writer, SkillCastType castType = SkillCastType.OnUse) + { + var context = new NpcExecutionContext(associate, writer); + + if (!RootBehaviors.TryGetValue(castType, out var list)) return context; + + foreach (var root in list) + { + context.Root = root; + + var branchContext = new ExecutionBranchContext(associate); + + await root.CalculateAsync(context, branchContext); + } + + return context; + } + /// + /// Execute a user preformed skill + /// + /// Executioner + /// Client skill data + /// Data to be sent to clients + /// Type of skill + /// + /// + /// Context public async Task ExecuteAsync(GameObject associate, BitReader reader, BitWriter writer, SkillCastType castType = SkillCastType.OnEquip, GameObject target = default, bool explicitTarget = false) { var context = new ExecutionContext(associate, reader, writer); diff --git a/Uchu.World/Behaviors/ExecutionContext.cs b/Uchu.World/Behaviors/ExecutionContext.cs index 075dc6be..5022f5cf 100644 --- a/Uchu.World/Behaviors/ExecutionContext.cs +++ b/Uchu.World/Behaviors/ExecutionContext.cs @@ -17,8 +17,6 @@ public class ExecutionContext public BitWriter Writer { get; set; } - public uint SkillId { get; set; } - public Dictionary BehaviorHandles { get; } = new Dictionary(); public ExecutionContext(GameObject associate, BitReader reader, BitWriter writer) diff --git a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs new file mode 100644 index 00000000..deffe1ce --- /dev/null +++ b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; + +namespace Uchu.World.Behaviors +{ + public class NpcCombatSkillBehavior : BehaviorBase + { + public override BehaviorTemplateId Id => BehaviorTemplateId.NPCCombatSkill; + + public BehaviorBase Behavior { get; set; } + + public float MinRange { get; set; } + + public float MaxRange { get; set; } + + public override async Task BuildAsync() + { + Behavior = await GetBehavior("Behavior 1"); + + MinRange = await GetParameter("min range"); + + MaxRange = await GetParameter("max range"); + } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + context.MinRange = MaxRange; + context.MaxRange = MaxRange; + + if (!context.Associate.TryGetComponent(out var baseCombatAiComponent)) return; + + await Behavior.CalculateAsync(context, branchContext); + } + } +} \ No newline at end of file diff --git a/Uchu.World/Behaviors/NpcExecutionContext.cs b/Uchu.World/Behaviors/NpcExecutionContext.cs new file mode 100644 index 00000000..552c7cf6 --- /dev/null +++ b/Uchu.World/Behaviors/NpcExecutionContext.cs @@ -0,0 +1,15 @@ +using RakDotNet.IO; + +namespace Uchu.World.Behaviors +{ + public class NpcExecutionContext : ExecutionContext + { + public float MinRange { get; set; } + + public float MaxRange { get; set; } + + public NpcExecutionContext(GameObject associate, BitWriter writer) : base(associate, default, writer) + { + } + } +} \ No newline at end of file diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 326be3d0..99c1d867 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Numerics; using System.Threading.Tasks; using Uchu.Core; @@ -18,6 +20,8 @@ public class TacArcBehavior : BehaviorBase public BehaviorBase MissBehavior { get; set; } + public int MaxTargets { get; set; } + public override async Task BuildAsync() { CheckEnvironment = (await GetParameter("check_env"))?.Value > 0; @@ -26,6 +30,8 @@ public override async Task BuildAsync() ActionBehavior = await GetBehavior("action"); BlockedBehavior = await GetBehavior("blocked action"); MissBehavior = await GetBehavior("miss action"); + + MaxTargets = await GetParameter("max targets"); } public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) @@ -101,5 +107,73 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc } } } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + if (!context.Associate.TryGetComponent(out var baseCombatAiComponent)) return; + + var validTarget = await baseCombatAiComponent.SeekValidTargetsAsync(); + + var sourcePosition = context.Associate.Transform.Position; + + var targets = validTarget.Where(target => + { + var transform = target.Transform; + + var distance = Vector3.Distance(transform.Position, sourcePosition); + + return context.MinRange < distance && distance < context.MaxRange; + }).ToArray(); + + var any = targets.Any(); + + context.Writer.WriteBit(any); // Hit + + if (!any) + { + if (Blocked) + { + context.Writer.WriteBit(false); + } + else + { + await MissBehavior.CalculateAsync(context, branchContext); + } + + return; + } + + if (CheckEnvironment) + { + context.Writer.WriteBit(false); + } + + var selectedTargets = new List(); + + foreach (var target in targets) + { + if (selectedTargets.Count < MaxTargets) + { + selectedTargets.Add(target); + } + } + + context.Writer.Write((uint) selectedTargets.Count); + + foreach (var target in selectedTargets) + { + context.Writer.Write(target.ObjectId); + } + + foreach (var target in selectedTargets) + { + var branch = new ExecutionBranchContext(target) + { + Duration = branchContext.Duration + }; + + await ActionBehavior.CalculateAsync(context, branch); + } + } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index a17ba790..2e1dbc5f 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Threading.Tasks; using RakDotNet.IO; namespace Uchu.World @@ -26,5 +28,12 @@ public override void Serialize(BitWriter writer) writer.Write((uint) Action); writer.Write(Target.ObjectId); } + + public async Task> SeekValidTargetsAsync() + { + // TODO: Do faction calculations + + return Zone.Players; + } } } \ No newline at end of file From e8c494f9de46e44463409d190f87d913208380b4 Mon Sep 17 00:00:00 2001 From: wincent Date: Thu, 6 Feb 2020 21:00:13 +0100 Subject: [PATCH 03/23] Added the ability to use the Python libraries: * Add a (path) between the tags. * For example /usr/lib/python2.7 to be able to use the Python 2.7 standard library in my case. Search for where your Python standard library is located. --- Uchu.Core/Config/Configuration.cs | 2 ++ Uchu.Python/ManagedScriptEngine.cs | 9 +++++++ Uchu.World/Behaviors/BehaviorTree.cs | 5 ++-- Uchu.World/Behaviors/NpcExecutionContext.cs | 25 ++++++++++++++++++- .../Commands/CharacterCommandHandler.cs | 22 ++++++++++++++-- Uchu.World/WorldServer.cs | 3 +++ 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Uchu.Core/Config/Configuration.cs b/Uchu.Core/Config/Configuration.cs index cae9b040..d5488fd2 100644 --- a/Uchu.Core/Config/Configuration.cs +++ b/Uchu.Core/Config/Configuration.cs @@ -43,6 +43,8 @@ public class Configuration public class ManagedScriptSources { [XmlElement("Script")] public List Scripts { get; set; } = new List(); + + [XmlElement("Library")] public List Paths { get; set; } = new List(); } public class ServerDllSource diff --git a/Uchu.Python/ManagedScriptEngine.cs b/Uchu.Python/ManagedScriptEngine.cs index 4b8b4937..2c11a881 100644 --- a/Uchu.Python/ManagedScriptEngine.cs +++ b/Uchu.Python/ManagedScriptEngine.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Microsoft.Scripting.Hosting; namespace Uchu.Python @@ -6,12 +7,20 @@ namespace Uchu.Python public class ManagedScriptEngine { public ScriptEngine Engine { get; private set; } + + public static string[] AdditionalPaths { get; set; } public void Init() { if (Engine != default) return; Engine = IronPython.Hosting.Python.CreateEngine(); + + var paths = Engine.GetSearchPaths().ToList(); + + paths.AddRange(AdditionalPaths); + + Engine.SetSearchPaths(paths); } public bool CompileScript(string script, out CompiledCode code, out ScriptScope scope) diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 2312747b..199376ef 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -152,11 +152,12 @@ public async Task BuildAsync() /// /// Executioner /// Data to be sent to clients + /// Skill to execute /// Type of skill /// Context - public async Task CalculateAsync(GameObject associate, BitWriter writer, SkillCastType castType = SkillCastType.OnUse) + public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, SkillCastType castType = SkillCastType.OnUse) { - var context = new NpcExecutionContext(associate, writer); + var context = new NpcExecutionContext(associate, writer, skillId); if (!RootBehaviors.TryGetValue(castType, out var list)) return context; diff --git a/Uchu.World/Behaviors/NpcExecutionContext.cs b/Uchu.World/Behaviors/NpcExecutionContext.cs index 552c7cf6..4499ad1a 100644 --- a/Uchu.World/Behaviors/NpcExecutionContext.cs +++ b/Uchu.World/Behaviors/NpcExecutionContext.cs @@ -1,3 +1,4 @@ +using System.IO; using RakDotNet.IO; namespace Uchu.World.Behaviors @@ -8,8 +9,30 @@ public class NpcExecutionContext : ExecutionContext public float MaxRange { get; set; } - public NpcExecutionContext(GameObject associate, BitWriter writer) : base(associate, default, writer) + public bool Start { get; set; } + + public int SkillId { get; set; } + + public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId) : base(associate, default, writer) + { + Start = true; + SkillId = skillId; + } + + public NpcExecutionContext Flush() { + if (Start) + { + Associate.Zone.BroadcastMessage(new EchoStartSkillMessage + { + SkillId = SkillId, + Associate = Associate, + CastType = (int) SkillCastType.OnUse, + Content = (Writer.BaseStream as MemoryStream)?.ToArray() + }); + } + + return this; } } } \ No newline at end of file diff --git a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs index 91d98ffb..465c8e27 100644 --- a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs @@ -527,10 +527,28 @@ public string Animate(string[] arguments, Player player) return "Invalid "; } } + + GameObject associate = player; + + if (arguments.Contains("-n")) + { + associate = player.Zone.GameObjects[0]; + + foreach (var gameObject in player.Zone.GameObjects.Where(g => g != player && g != default)) + { + if (gameObject.Transform == default) continue; + + if (gameObject.GetComponent() != default) continue; + + if (Vector3.Distance(associate.Transform.Position, player.Transform.Position) > + Vector3.Distance(gameObject.Transform.Position, player.Transform.Position)) + associate = gameObject; + } + } - player.Message(new PlayAnimationMessage + player.Zone.BroadcastMessage(new PlayAnimationMessage { - Associate = player, + Associate = associate, AnimationsId = arguments[0], Scale = scale }); diff --git a/Uchu.World/WorldServer.cs b/Uchu.World/WorldServer.cs index 17a878df..44235e7e 100644 --- a/Uchu.World/WorldServer.cs +++ b/Uchu.World/WorldServer.cs @@ -8,6 +8,7 @@ using RakDotNet; using RakDotNet.IO; using Uchu.Core; +using Uchu.Python; using Uchu.World.Client; using Uchu.World.Social; @@ -91,6 +92,8 @@ public override async Task ConfigureAsync(string configFile) await LoadZone(ServerSpecification); }); + + ManagedScriptEngine.AdditionalPaths = Config.ManagedScriptSources.Paths.ToArray(); Logger.Information($"Setting up world server: {ServerSpecification.Id}"); } From eeb22a327d0e4bb637b00df5ab75e00cadc57d5d Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Fri, 7 Feb 2020 09:18:15 +0100 Subject: [PATCH 04/23] More Python error logging --- Uchu.Python/ManagedScript.cs | 40 ++++++++++++++------ Uchu.World/Scripting/Managed/PythonScript.cs | 15 ++++++-- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/Uchu.Python/ManagedScript.cs b/Uchu.Python/ManagedScript.cs index 26533906..1c0a57b2 100644 --- a/Uchu.Python/ManagedScript.cs +++ b/Uchu.Python/ManagedScript.cs @@ -49,11 +49,13 @@ public bool Run(IEnumerable> variables) } } - public bool GetVariable(string name, out T result) + public bool GetVariable(string name, out T result, out Exception error) { if (_scope.GetItems().All(s => s.Key != name)) { result = default; + + error = default; return false; } @@ -63,23 +65,27 @@ public bool GetVariable(string name, out T result) try { result = (T) variable.Value; + + error = default; return true; } catch (Exception e) { - Console.WriteLine(e); - result = default; - + + error = e; + return false; } } - public bool Execute(string name) + public bool Execute(string name, out Exception error) { if (_scope.GetItems().All(s => s.Key != name)) { + error = default; + return false; } @@ -89,22 +95,26 @@ public bool Execute(string name) { variable.Value(); + error = default; + return true; } catch (Exception e) { - Console.WriteLine(e); + error = e; return false; } } - public bool Execute(string name, out TOut result) + public bool Execute(string name, out TOut result, out Exception error) { if (_scope.GetItems().All(s => s.Key != name)) { result = default; + error = default; + return false; } @@ -114,24 +124,28 @@ public bool Execute(string name, out TOut result) { result = (TOut) variable.Value(); + error = default; + return true; } catch (Exception e) { - Console.WriteLine(e); - result = default; + error = e; + return false; } } - public bool Execute(string name, out TOut result, TIn parameter) + public bool Execute(string name, out TOut result, TIn parameter, out Exception error) { if (_scope.GetItems().All(s => s.Key != name)) { result = default; + error = default; + return false; } @@ -141,14 +155,16 @@ public bool Execute(string name, out TOut result, TIn parameter) { result = (TOut) variable.Value(parameter); + error = default; + return true; } catch (Exception e) { - Console.WriteLine(e); - result = default; + error = e; + return false; } } diff --git a/Uchu.World/Scripting/Managed/PythonScript.cs b/Uchu.World/Scripting/Managed/PythonScript.cs index 72a346d1..3d40097a 100644 --- a/Uchu.World/Scripting/Managed/PythonScript.cs +++ b/Uchu.World/Scripting/Managed/PythonScript.cs @@ -155,11 +155,17 @@ public override Task LoadAsync() Task.Run(() => { - Script.Execute("load"); + Script.Execute("load", out var exception); + + if (exception != default) + Logger.Error(exception); }); Listen(Proxy.OnTick, () => { Task.Run(() => { - Script.Execute("tick"); + Script.Execute("tick", out var exception); + + if (exception != default) + Logger.Error(exception); }); }); @@ -174,7 +180,10 @@ public override Task UnloadAsync() Task.Run(() => { - Script.Execute("unload"); + Script.Execute("unload", out var exception); + + if (exception != default) + Logger.Error(exception); }); return Task.CompletedTask; From e8584a7b1bcde5113ac9943c6a6aa8283a678a19 Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 8 Feb 2020 18:33:51 +0100 Subject: [PATCH 05/23] Enemy skill execution: * Some basic behaviors can now be calculated server side. * Stromlings can execute their melee skill. --- Uchu.World/Behaviors/AttackDelayBehavior.cs | 26 ++++++++++ Uchu.World/Behaviors/BasicAttackBehavior.cs | 44 ++++++++++++++++ Uchu.World/Behaviors/BehaviorTree.cs | 37 ++++++++------ Uchu.World/Behaviors/ChainBehavior.cs | 9 ++++ Uchu.World/Behaviors/DurationBehavior.cs | 7 +++ Uchu.World/Behaviors/KnockbackBehavior.cs | 5 ++ .../Behaviors/NpcCombatSkillBehavior.cs | 5 +- Uchu.World/Behaviors/NpcExecutionContext.cs | 45 +++++++++++------ Uchu.World/Behaviors/StunBehavior.cs | 7 +++ Uchu.World/Behaviors/TacArcBehavior.cs | 11 +++- Uchu.World/Behaviors/VerifyBehavior.cs | 24 +++++++++ .../BaseCombatAIComponent.cs | 27 +++++++++- .../ReplicaComponents/SkillComponent.cs | 50 ++++++++++++++++++- 13 files changed, 261 insertions(+), 36 deletions(-) create mode 100644 Uchu.World/Behaviors/VerifyBehavior.cs diff --git a/Uchu.World/Behaviors/AttackDelayBehavior.cs b/Uchu.World/Behaviors/AttackDelayBehavior.cs index 6b43ddd7..3a1bef37 100644 --- a/Uchu.World/Behaviors/AttackDelayBehavior.cs +++ b/Uchu.World/Behaviors/AttackDelayBehavior.cs @@ -36,5 +36,31 @@ public override async Task SyncAsync(ExecutionContext context, ExecutionBranchCo { await Action.ExecuteAsync(context, branchContext); } + + public override Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + var syncId = context.Associate.GetComponent().ClaimSyncId(); + + context.Writer.Write(syncId); + + if (branchContext.Target is Player player) + player.SendChatMessage("Attack delay!"); + + Task.Run(async () => + { + await Task.Delay(Delay); + + context = context.Copy(); + + if (branchContext.Target is Player sPlayer) + sPlayer.SendChatMessage("Attack delay complete!"); + + await Action.CalculateAsync(context, branchContext); + + context.Sync(syncId); + }); + + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/BasicAttackBehavior.cs b/Uchu.World/Behaviors/BasicAttackBehavior.cs index 0a3ddb4a..e68b51c4 100644 --- a/Uchu.World/Behaviors/BasicAttackBehavior.cs +++ b/Uchu.World/Behaviors/BasicAttackBehavior.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Uchu.Core; namespace Uchu.World.Behaviors { @@ -8,9 +9,17 @@ public class BasicAttackBehavior : BehaviorBase public BehaviorBase OnSuccess { get; set; } + public int MinDamage { get; set; } + + public int MaxDamage { get; set; } + public override async Task BuildAsync() { OnSuccess = await GetBehavior("on_success"); + + MinDamage = await GetParameter("min damage"); + + MaxDamage = await GetParameter("max damage"); } public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) @@ -48,5 +57,40 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc await OnSuccess.ExecuteAsync(context, branchContext); } } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + Logger.Debug($"NPC is attacking: {branchContext.Target}"); + + context.Writer.Align(); + + context.Writer.Write(0); + + context.Writer.WriteBit(false); + context.Writer.WriteBit(false); + context.Writer.WriteBit(false); + + context.Writer.Write(0); + + var success = context.IsValidTarget(branchContext.Target); + + var damage = (uint) (success ? MinDamage : 0); + + context.Writer.Write(damage); + + if (branchContext.Target is Player player) + player.SendChatMessage($"Attacked for {damage}"); + + context.Writer.WriteBit(success); + + if (success) + { + var stats = branchContext.Target.GetComponent(); + + stats.Damage(damage, context.Associate); + + await OnSuccess.CalculateAsync(context, branchContext); + } + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 199376ef..412c8e8c 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -15,6 +15,8 @@ public class BehaviorTree private static Dictionary _behaviors; public (int behaviorId, SkillCastType castType, int skillId)[] BehaviorIds { get; } + + public Dictionary SkillRoots { get; set; } = new Dictionary(); public Dictionary> RootBehaviors { get; } = new Dictionary>(); @@ -66,9 +68,9 @@ public BehaviorTree(Lot lot) Logger.Information($"[{lot}] SKILL: {objectSkill.SkillID} -> {behavior.BehaviorID}"); BehaviorIds[index] = ( - behavior.BehaviorID.Value, - (SkillCastType) objectSkill.CastOnType.Value, - objectSkill.SkillID.Value + behavior.BehaviorID ?? 0, + objectSkill.CastOnType.HasValue ? (SkillCastType) objectSkill.CastOnType : SkillCastType.OnUse, + objectSkill.SkillID ?? 0 ); } } @@ -100,7 +102,7 @@ public async Task BuildAsync() { await using var ctx = new CdClientContext(); - foreach (var (id, castType, _) in BehaviorIds) + foreach (var (id, castType, skillId) in BehaviorIds) { var root = BehaviorBase.Cache.FirstOrDefault(b => b.BehaviorId == id); @@ -128,8 +130,12 @@ public async Task BuildAsync() BehaviorBase.Cache.Add(instance); await instance.BuildAsync(); + + root = instance; } + SkillRoots[skillId] = root; + if (RootBehaviors.TryGetValue(castType, out var list)) { list.Add(root); @@ -153,22 +159,25 @@ public async Task BuildAsync() /// Executioner /// Data to be sent to clients /// Skill to execute + /// Sync Id /// Type of skill /// Context - public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, SkillCastType castType = SkillCastType.OnUse) + public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, uint syncId, SkillCastType castType = SkillCastType.OnUse) { - var context = new NpcExecutionContext(associate, writer, skillId); - - if (!RootBehaviors.TryGetValue(castType, out var list)) return context; - - foreach (var root in list) + var context = new NpcExecutionContext(associate, writer, skillId, syncId); + + if (!SkillRoots.TryGetValue(skillId, out var root)) { - context.Root = root; - - var branchContext = new ExecutionBranchContext(associate); + Logger.Debug($"Failed to find skill: {skillId}"); - await root.CalculateAsync(context, branchContext); + return context; } + + context.Root = root; + + var branchContext = new ExecutionBranchContext(associate); + + await root.CalculateAsync(context, branchContext); return context; } diff --git a/Uchu.World/Behaviors/ChainBehavior.cs b/Uchu.World/Behaviors/ChainBehavior.cs index 7a1b78b7..7813cbba 100644 --- a/Uchu.World/Behaviors/ChainBehavior.cs +++ b/Uchu.World/Behaviors/ChainBehavior.cs @@ -45,5 +45,14 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc await Behaviors[chainIndex - 1].ExecuteAsync(context, branchContext); } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + // TODO + + context.Writer.Write(1); + + await Behaviors[1 - 1].CalculateAsync(context, branchContext); + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/DurationBehavior.cs b/Uchu.World/Behaviors/DurationBehavior.cs index 3f9e0577..41ab3fb8 100644 --- a/Uchu.World/Behaviors/DurationBehavior.cs +++ b/Uchu.World/Behaviors/DurationBehavior.cs @@ -29,5 +29,12 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc await Action.ExecuteAsync(context, branchContext); } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + branchContext.Duration = ActionDuration * 1000; + + await Action.CalculateAsync(context, branchContext); + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/KnockbackBehavior.cs b/Uchu.World/Behaviors/KnockbackBehavior.cs index c66d7851..41850ff3 100644 --- a/Uchu.World/Behaviors/KnockbackBehavior.cs +++ b/Uchu.World/Behaviors/KnockbackBehavior.cs @@ -19,5 +19,10 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Writer.WriteBit(false); } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + context.Writer.WriteBit(false); + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs index deffe1ce..773275c8 100644 --- a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs +++ b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Uchu.Core; namespace Uchu.World.Behaviors { @@ -14,7 +15,7 @@ public class NpcCombatSkillBehavior : BehaviorBase public override async Task BuildAsync() { - Behavior = await GetBehavior("Behavior 1"); + Behavior = await GetBehavior("behavior 1"); MinRange = await GetParameter("min range"); @@ -25,8 +26,6 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution { context.MinRange = MaxRange; context.MaxRange = MaxRange; - - if (!context.Associate.TryGetComponent(out var baseCombatAiComponent)) return; await Behavior.CalculateAsync(context, branchContext); } diff --git a/Uchu.World/Behaviors/NpcExecutionContext.cs b/Uchu.World/Behaviors/NpcExecutionContext.cs index 4499ad1a..30bc0385 100644 --- a/Uchu.World/Behaviors/NpcExecutionContext.cs +++ b/Uchu.World/Behaviors/NpcExecutionContext.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Numerics; using RakDotNet.IO; namespace Uchu.World.Behaviors @@ -9,30 +10,44 @@ public class NpcExecutionContext : ExecutionContext public float MaxRange { get; set; } - public bool Start { get; set; } - public int SkillId { get; set; } - public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId) : base(associate, default, writer) + public uint SyncSkillId { get; set; } + + public bool FoundTarget { get; set; } + + public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId, uint syncSkillId) : base(associate, default, writer) { - Start = true; SkillId = skillId; + SyncSkillId = syncSkillId; + } + + public void Sync(uint behaviorSyncId) + { + Associate.Zone.BroadcastMessage(new EchoSyncSkillMessage + { + Associate = Associate, + SkillHandle = SyncSkillId, + BehaviorHandle = behaviorSyncId, + Content = (Writer.BaseStream as MemoryStream)?.ToArray(), + Done = false + }); } - public NpcExecutionContext Flush() + public NpcExecutionContext Copy() { - if (Start) + return new NpcExecutionContext(Associate, new BitWriter(new MemoryStream()), SkillId, SyncSkillId) { - Associate.Zone.BroadcastMessage(new EchoStartSkillMessage - { - SkillId = SkillId, - Associate = Associate, - CastType = (int) SkillCastType.OnUse, - Content = (Writer.BaseStream as MemoryStream)?.ToArray() - }); - } + MaxRange = MaxRange, + MinRange = MinRange + }; + } + + public bool IsValidTarget(GameObject gameObject) + { + var distance = Vector3.Distance(gameObject.Transform.Position, Associate.Transform.Position); - return this; + return distance <= MaxRange; } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/StunBehavior.cs b/Uchu.World/Behaviors/StunBehavior.cs index d28bf1d9..c53cce60 100644 --- a/Uchu.World/Behaviors/StunBehavior.cs +++ b/Uchu.World/Behaviors/StunBehavior.cs @@ -25,5 +25,12 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Writer.WriteBit(false); } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + if (StunCaster == 1) return; + + context.Writer.WriteBit(false); + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 99c1d867..be9bf26a 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -122,7 +122,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution var distance = Vector3.Distance(transform.Position, sourcePosition); - return context.MinRange < distance && distance < context.MaxRange; + return distance <= context.MaxRange; }).ToArray(); var any = targets.Any(); @@ -143,6 +143,8 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution return; } + context.FoundTarget = true; + if (CheckEnvironment) { context.Writer.WriteBit(false); @@ -165,6 +167,13 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution context.Writer.Write(target.ObjectId); } + foreach (var target in selectedTargets) + { + if (!(target is Player player)) continue; + + player.SendChatMessage("You are a target!"); + } + foreach (var target in selectedTargets) { var branch = new ExecutionBranchContext(target) diff --git a/Uchu.World/Behaviors/VerifyBehavior.cs b/Uchu.World/Behaviors/VerifyBehavior.cs new file mode 100644 index 00000000..d4dd0121 --- /dev/null +++ b/Uchu.World/Behaviors/VerifyBehavior.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Uchu.World.Behaviors +{ + public class VerifyBehavior : BehaviorBase + { + public override BehaviorTemplateId Id => BehaviorTemplateId.Verify; + + public BehaviorBase Action { get; set; } + + public override async Task BuildAsync() + { + Action = await GetBehavior("action"); + } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + if (branchContext.Target is Player player) + player.SendChatMessage($"Verified: [{Action.Id}] {Action.BehaviorId}"); + + await Action.CalculateAsync(context, branchContext); + } + } +} \ No newline at end of file diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index 2e1dbc5f..47fd8d37 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using RakDotNet.IO; @@ -13,6 +14,28 @@ public class BaseCombatAiComponent : ReplicaComponent public GameObject Target { get; set; } public override ComponentId Id => ComponentId.BaseCombatAIComponent; + + public float Cooldown { get; set; } + + public BaseCombatAiComponent() + { + Listen(OnTick, async () => + { + var skillComponent = GameObject.GetComponent(); + + if (Cooldown <= 0) + { + foreach (var skillId in skillComponent.DefaultSkillSet) + { + await skillComponent.CalculateSkillAsync((int) skillId); + } + + Cooldown = 4; + } + + Cooldown -= Zone.DeltaTime; + }); + } public override void Construct(BitWriter writer) { @@ -29,11 +52,11 @@ public override void Serialize(BitWriter writer) writer.Write(Target.ObjectId); } - public async Task> SeekValidTargetsAsync() + public async Task SeekValidTargetsAsync() { // TODO: Do faction calculations - return Zone.Players; + return Zone.Players.ToArray(); } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index c666a21a..a1152a92 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -20,6 +20,10 @@ public class SkillComponent : ReplicaComponent // This number is taken from testing and is not concrete. public const float TargetRange = 11.6f; + private uint BehaviorSyncIndex { get; set; } + + public uint[] DefaultSkillSet { get; set; } + public Lot SelectedConsumeable { get; set; } public uint SelectedSkill @@ -32,8 +36,19 @@ public uint SelectedSkill protected SkillComponent() { - Listen(OnStart, () => + Listen(OnStart, async () => { + await using var cdClient = new CdClientContext(); + + var skills = await cdClient.ObjectSkillsTable.Where( + s => s.ObjectTemplate == GameObject.Lot + ).ToArrayAsync(); + + DefaultSkillSet = skills + .Where(s => s.SkillID != default) + .Select(s => (uint) s.SkillID) + .ToArray(); + if (!GameObject.TryGetComponent(out var inventory)) return; _activeBehaviors.Add(BehaviorSlot.Primary, 1); @@ -142,6 +157,31 @@ private async Task DismountSkill(Item item) await tree.DismantleAsync(GameObject); } + public async Task CalculateSkillAsync(int skillId) + { + var stream = new MemoryStream(); + using var writer = new BitWriter(stream, leaveOpen: true); + + var tree = new BehaviorTree(skillId); + + await tree.BuildAsync(); + + var syncId = ClaimSyncId(); + + var context = await tree.CalculateAsync(GameObject, writer, skillId, syncId); + + if (!context.FoundTarget) return; + + Zone.BroadcastMessage(new EchoStartSkillMessage + { + Associate = GameObject, + CastType = 0, + Content = stream.ToArray(), + SkillId = skillId, + SkillHandle = syncId, + }); + } + public async Task StartUserSkillAsync(StartSkillMessage message) { if (As() == null) return; @@ -305,5 +345,13 @@ public void RemoveSkill(BehaviorSlot slot) SkillId = skillId }); } + + public uint ClaimSyncId() + { + lock (this) + { + return ++BehaviorSyncIndex; + } + } } } \ No newline at end of file From d3e28ee8a10e3e7d039e7ccbf488f09fddaed434 Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 8 Feb 2020 23:15:03 +0100 Subject: [PATCH 06/23] Enemy skill execution: * Projectile attack calculation added. * Most enemies can now execute their attacks. * Simple cooldown system for enemies. --- InfectedRose | 2 +- Uchu.Auth/Handlers/LoginHandler.cs | 13 +++-- Uchu.World/Behaviors/AttackDelayBehavior.cs | 6 -- Uchu.World/Behaviors/BasicAttackBehavior.cs | 2 +- Uchu.World/Behaviors/BehaviorTree.cs | 12 ++-- .../Behaviors/NpcCombatSkillBehavior.cs | 6 +- Uchu.World/Behaviors/NpcExecutionContext.cs | 18 ++++++ .../Behaviors/ProjectileAttackBehavior.cs | 54 +++++++++++++++--- Uchu.World/Behaviors/SwitchBehavior.cs | 38 ++++++++++++- Uchu.World/Behaviors/TacArcBehavior.cs | 5 ++ Uchu.World/Behaviors/VerifyBehavior.cs | 2 +- .../Handlers/GameMessages/SkillHandler.cs | 2 +- .../BaseCombatAIComponent.cs | 55 +++++++++++++++++-- .../ReplicaComponents/SkillComponent.cs | 8 ++- Uchu.World/Objects/GameObjects/Projectile.cs | 39 +++++++++++-- 15 files changed, 220 insertions(+), 42 deletions(-) diff --git a/InfectedRose b/InfectedRose index 727a7640..863112bc 160000 --- a/InfectedRose +++ b/InfectedRose @@ -1 +1 @@ -Subproject commit 727a7640be8161fbfa7f83d6676d522206334482 +Subproject commit 863112bcd87d4f71904884aba1b15418e0bc2346 diff --git a/Uchu.Auth/Handlers/LoginHandler.cs b/Uchu.Auth/Handlers/LoginHandler.cs index a6fd1417..610a0cb4 100644 --- a/Uchu.Auth/Handlers/LoginHandler.cs +++ b/Uchu.Auth/Handlers/LoginHandler.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using RakDotNet; using Uchu.Core; +using static Uchu.Auth.ServerLoginInfoPacket; namespace Uchu.Auth.Handlers { @@ -9,7 +10,7 @@ public class LoginHandler : HandlerGroup { [PacketHandler] public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnection connection) - { + { await using var ctx = new UchuContext(); var info = new ServerLoginInfoPacket @@ -25,7 +26,7 @@ public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnecti if (characterSpecification == default) { info.LoginCode = LoginCode.InsufficientPermissions; - info.Error = new ServerLoginInfoPacket.ErrorMessage + info.Error = new ErrorMessage { Message = "No character server instance is running. Please try again later." }; @@ -37,7 +38,7 @@ public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnecti if (!await ctx.Users.AnyAsync(u => u.Username == packet.Username)) { info.LoginCode = LoginCode.InsufficientPermissions; - info.Error = new ServerLoginInfoPacket.ErrorMessage + info.Error = new ErrorMessage { Message = "We have no records of that Username and Password combination. Please try again." }; @@ -51,7 +52,7 @@ public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnecti if (user.Banned) { info.LoginCode = LoginCode.InsufficientPermissions; - info.Error = new ServerLoginInfoPacket.ErrorMessage + info.Error = new ErrorMessage { Message = $"This account has been banned by an admin. Reason:\n{user.BannedReason ?? "Unknown"}" }; @@ -59,7 +60,7 @@ public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnecti else if (!string.IsNullOrWhiteSpace(user.CustomLockout)) { info.LoginCode = LoginCode.InsufficientPermissions; - info.Error = new ServerLoginInfoPacket.ErrorMessage + info.Error = new ErrorMessage { Message = user.CustomLockout }; @@ -91,7 +92,7 @@ public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnecti else { info.LoginCode = LoginCode.InsufficientPermissions; - info.Error = new ServerLoginInfoPacket.ErrorMessage + info.Error = new ErrorMessage { Message = "We have no records of that Username and Password combination. Please try again." }; diff --git a/Uchu.World/Behaviors/AttackDelayBehavior.cs b/Uchu.World/Behaviors/AttackDelayBehavior.cs index 3a1bef37..ad123ece 100644 --- a/Uchu.World/Behaviors/AttackDelayBehavior.cs +++ b/Uchu.World/Behaviors/AttackDelayBehavior.cs @@ -43,18 +43,12 @@ public override Task CalculateAsync(NpcExecutionContext context, ExecutionBranch context.Writer.Write(syncId); - if (branchContext.Target is Player player) - player.SendChatMessage("Attack delay!"); - Task.Run(async () => { await Task.Delay(Delay); context = context.Copy(); - if (branchContext.Target is Player sPlayer) - sPlayer.SendChatMessage("Attack delay complete!"); - await Action.CalculateAsync(context, branchContext); context.Sync(syncId); diff --git a/Uchu.World/Behaviors/BasicAttackBehavior.cs b/Uchu.World/Behaviors/BasicAttackBehavior.cs index e68b51c4..b50fcda0 100644 --- a/Uchu.World/Behaviors/BasicAttackBehavior.cs +++ b/Uchu.World/Behaviors/BasicAttackBehavior.cs @@ -72,7 +72,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution context.Writer.Write(0); - var success = context.IsValidTarget(branchContext.Target); + var success = context.IsValidTarget(branchContext.Target) && context.Alive; var damage = (uint) (success ? MinDamage : 0); diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 412c8e8c..8d695cd9 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -102,7 +102,7 @@ public async Task BuildAsync() { await using var ctx = new CdClientContext(); - foreach (var (id, castType, skillId) in BehaviorIds) + foreach (var (id, castType, skillId) in BehaviorIds.ToArray()) { var root = BehaviorBase.Cache.FirstOrDefault(b => b.BehaviorId == id); @@ -160,11 +160,13 @@ public async Task BuildAsync() /// Data to be sent to clients /// Skill to execute /// Sync Id - /// Type of skill + /// Explicit target /// Context - public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, uint syncId, SkillCastType castType = SkillCastType.OnUse) + public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, uint syncId, GameObject target = default) { - var context = new NpcExecutionContext(associate, writer, skillId, syncId); + target ??= associate; + + var context = new NpcExecutionContext(target, writer, skillId, syncId); if (!SkillRoots.TryGetValue(skillId, out var root)) { @@ -175,7 +177,7 @@ public async Task CalculateAsync(GameObject associate, BitW context.Root = root; - var branchContext = new ExecutionBranchContext(associate); + var branchContext = new ExecutionBranchContext(target); await root.CalculateAsync(context, branchContext); diff --git a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs index 773275c8..2166fec8 100644 --- a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs +++ b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using Uchu.Core; namespace Uchu.World.Behaviors { @@ -13,6 +12,8 @@ public class NpcCombatSkillBehavior : BehaviorBase public float MaxRange { get; set; } + public float SkillTime { get; set; } + public override async Task BuildAsync() { Behavior = await GetBehavior("behavior 1"); @@ -20,12 +21,15 @@ public override async Task BuildAsync() MinRange = await GetParameter("min range"); MaxRange = await GetParameter("max range"); + + SkillTime = await GetParameter("npc skill time"); } public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) { context.MinRange = MaxRange; context.MaxRange = MaxRange; + context.SkillTime = SkillTime; await Behavior.CalculateAsync(context, branchContext); } diff --git a/Uchu.World/Behaviors/NpcExecutionContext.cs b/Uchu.World/Behaviors/NpcExecutionContext.cs index 30bc0385..8d70731d 100644 --- a/Uchu.World/Behaviors/NpcExecutionContext.cs +++ b/Uchu.World/Behaviors/NpcExecutionContext.cs @@ -10,11 +10,29 @@ public class NpcExecutionContext : ExecutionContext public float MaxRange { get; set; } + public float SkillTime { get; set; } + public int SkillId { get; set; } public uint SyncSkillId { get; set; } public bool FoundTarget { get; set; } + + public bool Alive + { + get + { + var destructComponent = Associate.GetComponent(); + + var rebuild = Associate.GetComponent(); + + if (!destructComponent.Alive) return false; + + if (rebuild != default && rebuild.State != RebuildState.Completed) return false; + + return true; + } + } public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId, uint syncSkillId) : base(associate, default, writer) { diff --git a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs index b679fe21..a68354ac 100644 --- a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs +++ b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs @@ -1,4 +1,6 @@ +using System.Numerics; using System.Threading.Tasks; +using Uchu.Core; namespace Uchu.World.Behaviors { @@ -9,12 +11,16 @@ public class ProjectileAttackBehavior : BehaviorBase public int ProjectileCount { get; set; } public Lot ProjectileLot { get; set; } + + public float ProjectileSpeed { get; set; } public override async Task BuildAsync() { ProjectileCount = await GetParameter("spread_count"); ProjectileLot = await GetParameter("LOT_ID"); + + ProjectileSpeed = await GetParameter("projectile_speed"); } public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) @@ -26,19 +32,53 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Writer.Write(target); ((Player) context.Associate)?.SendChatMessage($"{ProjectileCount} projectiles."); + + var count = ProjectileCount == 0 ? 1 : ProjectileCount; - if (ProjectileCount == 0) + for (var i = 0; i < count; i++) { StartProjectile(context, target); } - else + } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + context.Writer.Write(branchContext.Target); + + var count = ProjectileCount == 0 ? 1 : ProjectileCount; + + for (var i = 0; i < count; i++) { - for (var i = 0; i < ProjectileCount; i++) - { - StartProjectile(context, target); - } + CalculateProjectile(context, branchContext.Target); } } + + private void CalculateProjectile(ExecutionContext context, GameObject target) + { + var projectileId = IdUtilities.GenerateObjectId(); + + context.Writer.Write(projectileId); + + var projectile = Object.Instantiate(context.Associate.Zone); + + projectile.Owner = context.Associate; + projectile.ClientObjectId = projectileId; + projectile.Target = target; + projectile.Lot = ProjectileLot; + + Object.Start(projectile); + + Task.Run(async () => + { + var distance = Vector3.Distance(context.Associate.Transform.Position, target.Transform.Position); + + var time = (int) (distance / (double) ProjectileSpeed) * 1000; + + await Task.Delay(time); + + await projectile.CalculateImpactAsync(target); + }); + } private void StartProjectile(ExecutionContext context, GameObject target) { @@ -52,7 +92,7 @@ private void StartProjectile(ExecutionContext context, GameObject target) projectile.ClientObjectId = projectileId; projectile.Target = target; projectile.Lot = ProjectileLot; - + ((Player) context.Associate)?.SendChatMessage($"Start PROJ: [{projectile.Lot}] {projectile.ClientObjectId} -> {projectile.Target}"); Object.Start(projectile); diff --git a/Uchu.World/Behaviors/SwitchBehavior.cs b/Uchu.World/Behaviors/SwitchBehavior.cs index 71cf70e8..6911b02d 100644 --- a/Uchu.World/Behaviors/SwitchBehavior.cs +++ b/Uchu.World/Behaviors/SwitchBehavior.cs @@ -43,7 +43,7 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc { state = context.Reader.ReadBit(); - context.Writer.WriteBit(false); + context.Writer.WriteBit(state); } if (state) @@ -56,6 +56,42 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc } } + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + var syncId = context.Associate.GetComponent().ClaimSyncId(); + + context.Writer.Write(syncId); + + // TODO + + var state = branchContext.Target != default && context.Alive; + + if (Imagination > 0 || !IsEnemyFaction) + { + context.Writer.WriteBit(true); + + state = true; + } + + if (state) + { + await ActionTrue.CalculateAsync(context, branchContext); + } + else + { + await ActionFalse.CalculateAsync(context, branchContext); + } + + var _ = Task.Run(async () => + { + context = context.Copy(); + + await Action.CalculateAsync(context, branchContext); + + context.Sync(syncId); + }); + } + public override async Task SyncAsync(ExecutionContext context, ExecutionBranchContext branchContext) { await Action.ExecuteAsync(context, branchContext); diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index be9bf26a..019c8a4c 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -125,6 +125,11 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution return distance <= context.MaxRange; }).ToArray(); + if (!context.Alive) + { + targets = new GameObject[0]; // No targeting if dead + } + var any = targets.Any(); context.Writer.WriteBit(any); // Hit diff --git a/Uchu.World/Behaviors/VerifyBehavior.cs b/Uchu.World/Behaviors/VerifyBehavior.cs index d4dd0121..de28be09 100644 --- a/Uchu.World/Behaviors/VerifyBehavior.cs +++ b/Uchu.World/Behaviors/VerifyBehavior.cs @@ -17,7 +17,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution { if (branchContext.Target is Player player) player.SendChatMessage($"Verified: [{Action.Id}] {Action.BehaviorId}"); - + await Action.CalculateAsync(context, branchContext); } } diff --git a/Uchu.World/Handlers/GameMessages/SkillHandler.cs b/Uchu.World/Handlers/GameMessages/SkillHandler.cs index 3fafe5ce..5704de2a 100644 --- a/Uchu.World/Handlers/GameMessages/SkillHandler.cs +++ b/Uchu.World/Handlers/GameMessages/SkillHandler.cs @@ -64,7 +64,7 @@ public async Task ServerProjectileImpactHandler(RequestServerProjectileImpactMes if (projectile == default) return; - await projectile.Impact(message.Data, message.Target); + await projectile.ImpactAsync(message.Data, message.Target); } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index 47fd8d37..44ef6619 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using RakDotNet.IO; +using Uchu.Core.Client; +using Uchu.World.Scripting.Native; namespace Uchu.World { @@ -19,18 +21,63 @@ public class BaseCombatAiComponent : ReplicaComponent public BaseCombatAiComponent() { + var idle = true; + Listen(OnTick, async () => { var skillComponent = GameObject.GetComponent(); + var destructComponent = GameObject.GetComponent(); + + var rebuild = GameObject.GetComponent(); + + if (!destructComponent.Alive) return; + + if (rebuild != default && rebuild.State != RebuildState.Completed) return; + if (Cooldown <= 0) { - foreach (var skillId in skillComponent.DefaultSkillSet) + if (!idle) { - await skillComponent.CalculateSkillAsync((int) skillId); + GameObject.Animate("idle", true); + + idle = true; } + + await using var ctx = new CdClientContext(); + + Cooldown = 0.5f; + + foreach (var skillId in skillComponent.DefaultSkillSet) + { + var time = await skillComponent.CalculateSkillAsync((int) skillId); - Cooldown = 4; + if (time.Equals(0)) continue; + + GameObject.Animate("attack", true); + + idle = false; + + var skillInfo = await ctx.SkillBehaviorTable.FirstAsync( + s => s.SkillID == skillId + ); + + var _ = Task.Run(async () => + { + await Task.Delay((int) (time * 1000)); + + if (!idle) + { + GameObject.Animate("idle", true); + + idle = true; + } + }); + + Cooldown = (skillInfo.Cooldown ?? 0.5f) + time; + + break; + } } Cooldown -= Zone.DeltaTime; diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index a1152a92..a5a04833 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -157,7 +157,7 @@ private async Task DismountSkill(Item item) await tree.DismantleAsync(GameObject); } - public async Task CalculateSkillAsync(int skillId) + public async Task CalculateSkillAsync(int skillId) { var stream = new MemoryStream(); using var writer = new BitWriter(stream, leaveOpen: true); @@ -169,8 +169,8 @@ public async Task CalculateSkillAsync(int skillId) var syncId = ClaimSyncId(); var context = await tree.CalculateAsync(GameObject, writer, skillId, syncId); - - if (!context.FoundTarget) return; + + if (!context.FoundTarget) return 0; Zone.BroadcastMessage(new EchoStartSkillMessage { @@ -180,6 +180,8 @@ public async Task CalculateSkillAsync(int skillId) SkillId = skillId, SkillHandle = syncId, }); + + return context.SkillTime; } public async Task StartUserSkillAsync(StartSkillMessage message) diff --git a/Uchu.World/Objects/GameObjects/Projectile.cs b/Uchu.World/Objects/GameObjects/Projectile.cs index cc24c8f6..13ccb782 100644 --- a/Uchu.World/Objects/GameObjects/Projectile.cs +++ b/Uchu.World/Objects/GameObjects/Projectile.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using System.Threading.Tasks; using RakDotNet.IO; using Uchu.World.Behaviors; @@ -15,7 +16,7 @@ public class Projectile : Object public GameObject Target { get; set; } - public async Task Impact(byte[] data, GameObject target) + public async Task ImpactAsync(byte[] data, GameObject target) { target ??= Target; @@ -23,15 +24,13 @@ public async Task Impact(byte[] data, GameObject target) await tree.BuildAsync(); - var stream = new MemoryStream(data); + await using var stream = new MemoryStream(data); var reader = new BitReader(stream); - var writeStream = new MemoryStream(); + await using var writeStream = new MemoryStream(); var writer = new BitWriter(writeStream); - - ((Player) Owner)?.SendChatMessage($"Projectile HIT [{Lot}, {tree.RootBehaviors.Count}] -> {target}"); await tree.UseAsync(Owner, reader, writer, target); @@ -44,5 +43,35 @@ public async Task Impact(byte[] data, GameObject target) Target = target }); } + + public async Task CalculateImpactAsync(GameObject target) + { + target ??= Target; + + var tree = new BehaviorTree(Lot); + + await tree.BuildAsync(); + + await using var stream = new MemoryStream(); + + var writer = new BitWriter(stream); + + await tree.CalculateAsync( + Owner, + writer, + tree.SkillRoots.First().Key, + Owner.GetComponent().ClaimSyncId(), + target + ); + + Zone.BroadcastMessage(new DoClientProjectileImpact + { + Associate = Owner, + Data = stream.ToArray(), + Owner = Owner, + ProjectileId = ClientObjectId, + Target = target + }); + } } } \ No newline at end of file From e9df1ab347ca565c0d1438744c3ac586053f1699 Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 8 Feb 2020 23:58:42 +0100 Subject: [PATCH 07/23] Added minimum range check for npc skills --- Uchu.World/Behaviors/NpcCombatSkillBehavior.cs | 2 +- Uchu.World/Behaviors/TacArcBehavior.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs index 2166fec8..c7e43090 100644 --- a/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs +++ b/Uchu.World/Behaviors/NpcCombatSkillBehavior.cs @@ -27,7 +27,7 @@ public override async Task BuildAsync() public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) { - context.MinRange = MaxRange; + context.MinRange = MinRange; context.MaxRange = MaxRange; context.SkillTime = SkillTime; diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 019c8a4c..961916b0 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -122,7 +122,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution var distance = Vector3.Distance(transform.Position, sourcePosition); - return distance <= context.MaxRange; + return distance <= context.MaxRange && context.MinRange <= distance; }).ToArray(); if (!context.Alive) From a29512f729759a35b58bfd532d62afbde0d81718 Mon Sep 17 00:00:00 2001 From: wincent Date: Sun, 9 Feb 2020 13:08:47 +0100 Subject: [PATCH 08/23] Enemy skill execution: * Fixed a bug where the client would not register skills * Improved serialization of calculated behaviors * Damage impact on player (not sure I'm doing it right) --- .../Extensions/RakConnectionExtensions.cs | 10 ++- Uchu.World/Behaviors/AttackDelayBehavior.cs | 10 +++ Uchu.World/Behaviors/BasicAttackBehavior.cs | 4 +- Uchu.World/Behaviors/BehaviorTree.cs | 2 +- Uchu.World/Behaviors/InterruptBehavior.cs | 15 ++++ Uchu.World/Behaviors/NpcExecutionContext.cs | 14 ++-- Uchu.World/Behaviors/SwitchBehavior.cs | 8 +- Uchu.World/Behaviors/TacArcBehavior.cs | 84 ++++++++++--------- .../BaseCombatAIComponent.cs | 11 ++- .../ReplicaComponents/SkillComponent.cs | 12 ++- Uchu.World/Objects/Components/Stats.cs | 6 ++ .../GameMessages/Client/StartSkillMessage.cs | 40 ++++++++- .../GameMessages/Client/SyncSkillMessage.cs | 20 ++++- 13 files changed, 167 insertions(+), 69 deletions(-) diff --git a/Uchu.Core/Extensions/RakConnectionExtensions.cs b/Uchu.Core/Extensions/RakConnectionExtensions.cs index efc487d6..497d1807 100644 --- a/Uchu.Core/Extensions/RakConnectionExtensions.cs +++ b/Uchu.Core/Extensions/RakConnectionExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using RakDotNet; using RakDotNet.IO; @@ -15,7 +16,14 @@ public static void Send(this IRakConnection @this, ISerializable serializable) writer.Write(serializable); - @this.Send(stream.ToArray()); + try + { + @this.Send(stream.ToArray()); + } + catch (IOException e) + { + Logger.Error(e); + } } public static void SavePacket(this IRakConnection @this, ISerializable serializable) diff --git a/Uchu.World/Behaviors/AttackDelayBehavior.cs b/Uchu.World/Behaviors/AttackDelayBehavior.cs index ad123ece..0405be44 100644 --- a/Uchu.World/Behaviors/AttackDelayBehavior.cs +++ b/Uchu.World/Behaviors/AttackDelayBehavior.cs @@ -43,6 +43,11 @@ public override Task CalculateAsync(NpcExecutionContext context, ExecutionBranch context.Writer.Write(syncId); + if (branchContext.Target is Player player) + { + player.SendChatMessage($"Delay. [{context.SkillSyncId}] [{syncId}]"); + } + Task.Run(async () => { await Task.Delay(Delay); @@ -52,6 +57,11 @@ public override Task CalculateAsync(NpcExecutionContext context, ExecutionBranch await Action.CalculateAsync(context, branchContext); context.Sync(syncId); + + if (branchContext.Target is Player sPlayer) + { + sPlayer.SendChatMessage($"Sync. [{context.SkillSyncId}] [{syncId}]"); + } }); return Task.CompletedTask; diff --git a/Uchu.World/Behaviors/BasicAttackBehavior.cs b/Uchu.World/Behaviors/BasicAttackBehavior.cs index b50fcda0..d3490574 100644 --- a/Uchu.World/Behaviors/BasicAttackBehavior.cs +++ b/Uchu.World/Behaviors/BasicAttackBehavior.cs @@ -37,7 +37,7 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Reader.ReadBit(); context.Writer.WriteBit(false); context.Writer.WriteBit(false); - context.Writer.WriteBit(false); + context.Writer.WriteBit(true); context.Reader.Read(); context.Writer.Write(0); @@ -68,7 +68,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution context.Writer.WriteBit(false); context.Writer.WriteBit(false); - context.Writer.WriteBit(false); + context.Writer.WriteBit(true); context.Writer.Write(0); diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 8d695cd9..5b310a6b 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -104,7 +104,7 @@ public async Task BuildAsync() foreach (var (id, castType, skillId) in BehaviorIds.ToArray()) { - var root = BehaviorBase.Cache.FirstOrDefault(b => b.BehaviorId == id); + var root = BehaviorBase.Cache.ToArray().FirstOrDefault(b => b.BehaviorId == id); if (root == default) { diff --git a/Uchu.World/Behaviors/InterruptBehavior.cs b/Uchu.World/Behaviors/InterruptBehavior.cs index d8dc6c62..04caf1ad 100644 --- a/Uchu.World/Behaviors/InterruptBehavior.cs +++ b/Uchu.World/Behaviors/InterruptBehavior.cs @@ -43,5 +43,20 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc context.Writer.WriteBit(false); } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + if (branchContext.Target != context.Associate) + { + context.Writer.WriteBit(false); + } + + if (InterruptBlock == 0) + { + context.Writer.WriteBit(false); + } + + context.Writer.WriteBit(false); + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/NpcExecutionContext.cs b/Uchu.World/Behaviors/NpcExecutionContext.cs index 8d70731d..65bbf84e 100644 --- a/Uchu.World/Behaviors/NpcExecutionContext.cs +++ b/Uchu.World/Behaviors/NpcExecutionContext.cs @@ -14,7 +14,7 @@ public class NpcExecutionContext : ExecutionContext public int SkillId { get; set; } - public uint SyncSkillId { get; set; } + public uint SkillSyncId { get; set; } public bool FoundTarget { get; set; } @@ -34,10 +34,10 @@ public bool Alive } } - public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId, uint syncSkillId) : base(associate, default, writer) + public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId, uint skillSyncId) : base(associate, default, writer) { SkillId = skillId; - SyncSkillId = syncSkillId; + SkillSyncId = skillSyncId; } public void Sync(uint behaviorSyncId) @@ -45,16 +45,16 @@ public void Sync(uint behaviorSyncId) Associate.Zone.BroadcastMessage(new EchoSyncSkillMessage { Associate = Associate, - SkillHandle = SyncSkillId, - BehaviorHandle = behaviorSyncId, + SkillHandle = SkillSyncId, Content = (Writer.BaseStream as MemoryStream)?.ToArray(), - Done = false + Done = true, + BehaviorHandle = behaviorSyncId }); } public NpcExecutionContext Copy() { - return new NpcExecutionContext(Associate, new BitWriter(new MemoryStream()), SkillId, SyncSkillId) + return new NpcExecutionContext(Associate, new BitWriter(new MemoryStream()), SkillId, SkillSyncId) { MaxRange = MaxRange, MinRange = MinRange diff --git a/Uchu.World/Behaviors/SwitchBehavior.cs b/Uchu.World/Behaviors/SwitchBehavior.cs index 6911b02d..e5cc9328 100644 --- a/Uchu.World/Behaviors/SwitchBehavior.cs +++ b/Uchu.World/Behaviors/SwitchBehavior.cs @@ -64,13 +64,13 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution // TODO - var state = branchContext.Target != default && context.Alive; + var state = true; if (Imagination > 0 || !IsEnemyFaction) { - context.Writer.WriteBit(true); - - state = true; + state = branchContext.Target != default && context.Alive; + + context.Writer.WriteBit(state); } if (state) diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 961916b0..21071153 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -125,68 +125,70 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution return distance <= context.MaxRange && context.MinRange <= distance; }).ToArray(); + var selectedTargets = new List(); + + foreach (var target in targets) + { + if (selectedTargets.Count < MaxTargets) + { + selectedTargets.Add(target); + } + } + if (!context.Alive) { - targets = new GameObject[0]; // No targeting if dead + selectedTargets.Clear(); // No targeting if dead } - var any = targets.Any(); - + var any = selectedTargets.Any(); + context.Writer.WriteBit(any); // Hit - if (!any) + if (any) { - if (Blocked) + context.FoundTarget = true; + + if (CheckEnvironment) { + // TODO context.Writer.WriteBit(false); } - else - { - await MissBehavior.CalculateAsync(context, branchContext); - } - - return; - } - - context.FoundTarget = true; - - if (CheckEnvironment) - { - context.Writer.WriteBit(false); - } - var selectedTargets = new List(); + context.Writer.Write((uint) selectedTargets.Count); - foreach (var target in targets) - { - if (selectedTargets.Count < MaxTargets) + foreach (var target in selectedTargets) { - selectedTargets.Add(target); + context.Writer.Write(target.ObjectId); } - } - context.Writer.Write((uint) selectedTargets.Count); + foreach (var target in selectedTargets) + { + if (!(target is Player player)) continue; - foreach (var target in selectedTargets) - { - context.Writer.Write(target.ObjectId); - } + player.SendChatMessage($"You are a target! [{context.SkillSyncId}]"); + } - foreach (var target in selectedTargets) - { - if (!(target is Player player)) continue; + foreach (var target in selectedTargets) + { + var branch = new ExecutionBranchContext(target) + { + Duration = branchContext.Duration + }; - player.SendChatMessage("You are a target!"); + await ActionBehavior.CalculateAsync(context, branch); + } } - - foreach (var target in selectedTargets) + else { - var branch = new ExecutionBranchContext(target) + if (Blocked) { - Duration = branchContext.Duration - }; - - await ActionBehavior.CalculateAsync(context, branch); + // TODO + context.Writer.WriteBit(false); + } + else + { + await MissBehavior.CalculateAsync(context, branchContext); + } } } } diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index 44ef6619..463636a9 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore; using RakDotNet.IO; using Uchu.Core.Client; -using Uchu.World.Scripting.Native; namespace Uchu.World { @@ -39,7 +38,7 @@ public BaseCombatAiComponent() { if (!idle) { - GameObject.Animate("idle", true); + Action = CombatAiAction.Idle; idle = true; } @@ -54,7 +53,7 @@ public BaseCombatAiComponent() if (time.Equals(0)) continue; - GameObject.Animate("attack", true); + Action = CombatAiAction.Attacking; idle = false; @@ -66,9 +65,9 @@ public BaseCombatAiComponent() { await Task.Delay((int) (time * 1000)); - if (!idle) + if (!PerformingAction) { - GameObject.Animate("idle", true); + Action = CombatAiAction.Idle; idle = true; } @@ -96,7 +95,7 @@ public override void Serialize(BitWriter writer) if (!PerformingAction) return; writer.Write((uint) Action); - writer.Write(Target.ObjectId); + writer.Write(Target); } public async Task SeekValidTargetsAsync() diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index a5a04833..fa20da24 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -171,6 +171,11 @@ public async Task CalculateSkillAsync(int skillId) var context = await tree.CalculateAsync(GameObject, writer, skillId, syncId); if (!context.FoundTarget) return 0; + + foreach (var player in Zone.Players) + { + player.SendChatMessage($"Start: [{syncId}]"); + } Zone.BroadcastMessage(new EchoStartSkillMessage { @@ -179,6 +184,7 @@ public async Task CalculateSkillAsync(int skillId) Content = stream.ToArray(), SkillId = skillId, SkillHandle = syncId, + OptionalOriginator = GameObject }); return context.SkillTime; @@ -254,11 +260,11 @@ public async Task SyncUserSkillAsync(SyncSkillMessage message) var found = _handledSkills.TryGetValue(message.SkillHandle, out var behavior); - As().SendChatMessage($"SYNC: {message.SkillHandle} [{message.BehaviourHandle}] ; {found}"); + As().SendChatMessage($"SYNC: {message.SkillHandle} [{message.BehaviorHandle}] ; {found}"); if (found) { - await behavior.SyncAsync(message.BehaviourHandle, reader, writer); + await behavior.SyncAsync(message.BehaviorHandle, reader, writer); } if (message.Done) @@ -269,7 +275,7 @@ public async Task SyncUserSkillAsync(SyncSkillMessage message) Zone.ExcludingMessage(new EchoSyncSkillMessage { Associate = GameObject, - BehaviorHandle = message.BehaviourHandle, + BehaviorHandle = message.BehaviorHandle, Content = writeStream.ToArray(), Done = message.Done, SkillHandle = message.SkillHandle diff --git a/Uchu.World/Objects/Components/Stats.cs b/Uchu.World/Objects/Components/Stats.cs index 09ee6962..798afe1a 100644 --- a/Uchu.World/Objects/Components/Stats.cs +++ b/Uchu.World/Objects/Components/Stats.cs @@ -5,6 +5,7 @@ using RakDotNet.IO; using Uchu.Core; using Uchu.Core.Client; +using Uchu.World.Scripting.Native; namespace Uchu.World { @@ -298,6 +299,11 @@ public void Damage(uint value, GameObject source) Armor -= armorDamage; Health -= Math.Min(value, Health); + + if (source != default && GameObject is Player) + { + GameObject.Animate("onhit", true); + } } public void Heal(uint value) diff --git a/Uchu.World/Packets/GameMessages/Client/StartSkillMessage.cs b/Uchu.World/Packets/GameMessages/Client/StartSkillMessage.cs index 1afcf6a0..80913329 100644 --- a/Uchu.World/Packets/GameMessages/Client/StartSkillMessage.cs +++ b/Uchu.World/Packets/GameMessages/Client/StartSkillMessage.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Numerics; using RakDotNet.IO; @@ -5,7 +6,7 @@ namespace Uchu.World { - public class StartSkillMessage : ClientGameMessage + public class StartSkillMessage : GeneralGameMessage { public override GameMessageId GameMessageId => GameMessageId.StartSkill; @@ -62,5 +63,42 @@ public override void Deserialize(BitReader reader) if (reader.ReadBit()) SkillHandle = reader.Read(); } + + public override void SerializeMessage(BitWriter writer) + { + writer.WriteBit(UsedMouse); + + if (writer.Flag(ConsumableItem != default)) + writer.Write(ConsumableItem); + + if (writer.Flag(!CasterLatency.Equals(0))) + writer.Write(CasterLatency); + + if (writer.Flag(CastType != default)) + writer.Write(CastType); + + if (writer.Flag(LastClickedPosition != Vector3.Zero)) + writer.Write(LastClickedPosition); + + writer.Write(OptionalOriginator); + + if (writer.Flag(OptionalTarget != default)) + writer.Write(OptionalTarget); + + if (writer.Flag(OriginatorRotation != Quaternion.Identity)) + writer.Write(OriginatorRotation); + + writer.Write((uint) Content.Length); + + foreach (var b in Content) + { + writer.Write(b); + } + + writer.Write(SkillId); + + if (writer.Flag(SkillHandle != 0)) + writer.Write(SkillHandle); + } } } \ No newline at end of file diff --git a/Uchu.World/Packets/GameMessages/Client/SyncSkillMessage.cs b/Uchu.World/Packets/GameMessages/Client/SyncSkillMessage.cs index 52852836..0c11e997 100644 --- a/Uchu.World/Packets/GameMessages/Client/SyncSkillMessage.cs +++ b/Uchu.World/Packets/GameMessages/Client/SyncSkillMessage.cs @@ -2,7 +2,7 @@ namespace Uchu.World { - public class SyncSkillMessage : ClientGameMessage + public class SyncSkillMessage : GeneralGameMessage { public override GameMessageId GameMessageId => GameMessageId.SyncSkill; @@ -10,7 +10,7 @@ public class SyncSkillMessage : ClientGameMessage public byte[] Content { get; set; } - public uint BehaviourHandle { get; set; } + public uint BehaviorHandle { get; set; } public uint SkillHandle { get; set; } @@ -22,8 +22,22 @@ public override void Deserialize(BitReader reader) for (var i = 0; i < Content.Length; i++) Content[i] = reader.Read(); - BehaviourHandle = reader.Read(); + BehaviorHandle = reader.Read(); SkillHandle = reader.Read(); } + + public override void SerializeMessage(BitWriter writer) + { + writer.WriteBit(Done); + + writer.Write((uint) Content.Length); + + foreach (var b in Content) + writer.Write(b); + + writer.Write(BehaviorHandle); + + writer.Write(SkillHandle); + } } } \ No newline at end of file From 133d64d38d40602f4faf142424f47f87b7de29a3 Mon Sep 17 00:00:00 2001 From: wincent Date: Sun, 9 Feb 2020 14:00:37 +0100 Subject: [PATCH 09/23] Forgot to push these migrations --- .../20200201151430_MySqlGuild.Designer.cs | 514 +++++++++++++++++ .../MySql/20200201151430_MySqlGuild.cs | 70 +++ .../20200201151447_PostgresGuild.Designer.cs | 517 ++++++++++++++++++ .../Postgres/20200201151447_PostgresGuild.cs | 70 +++ 4 files changed, 1171 insertions(+) create mode 100644 Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.Designer.cs create mode 100644 Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.cs create mode 100644 Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.Designer.cs create mode 100644 Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.cs diff --git a/Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.Designer.cs b/Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.Designer.cs new file mode 100644 index 00000000..621ab755 --- /dev/null +++ b/Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.Designer.cs @@ -0,0 +1,514 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Uchu.Core.Providers; + +namespace Uchu.Core.Migrations.MySql +{ + [DbContext(typeof(MySqlContext))] + [Migration("20200201151430_MySqlGuild")] + partial class MySqlGuild + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.Property("CharacterId") + .ValueGeneratedOnAdd(); + + b.Property("BaseHealth"); + + b.Property("BaseImagination"); + + b.Property("Currency"); + + b.Property("CurrentArmor"); + + b.Property("CurrentHealth"); + + b.Property("CurrentImagination"); + + b.Property("CustomName") + .IsRequired() + .HasMaxLength(33); + + b.Property("EyeStyle"); + + b.Property("EyebrowStyle"); + + b.Property("FreeToPlay"); + + b.Property("GuildId"); + + b.Property("HairColor"); + + b.Property("HairStyle"); + + b.Property("InventorySize"); + + b.Property("LandingByRocket"); + + b.Property("LastActivity"); + + b.Property("LastClone"); + + b.Property("LastInstance"); + + b.Property("LastZone"); + + b.Property("LaunchedRocketFrom"); + + b.Property("Level"); + + b.Property("Lh"); + + b.Property("MaximumArmor"); + + b.Property("MaximumHealth"); + + b.Property("MaximumImagination"); + + b.Property("MouthStyle"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(33); + + b.Property("NameRejected"); + + b.Property("PantsColor"); + + b.Property("Rh"); + + b.Property("Rocket") + .HasMaxLength(30); + + b.Property("ShirtColor"); + + b.Property("ShirtStyle"); + + b.Property("TotalArmorPowerUpsCollected"); + + b.Property("TotalArmorRepaired"); + + b.Property("TotalBricksCollected"); + + b.Property("TotalCurrencyCollected"); + + b.Property("TotalDamageHealed"); + + b.Property("TotalDamageTaken"); + + b.Property("TotalDistanceDriven"); + + b.Property("TotalDistanceTraveled"); + + b.Property("TotalEnemiesSmashed"); + + b.Property("TotalFirstPlaceFinishes"); + + b.Property("TotalImaginationPowerUpsCollected"); + + b.Property("TotalImaginationRestored"); + + b.Property("TotalImaginationUsed"); + + b.Property("TotalLifePowerUpsCollected"); + + b.Property("TotalMissionsCompleted"); + + b.Property("TotalPetsTamed"); + + b.Property("TotalQuickBuildsCompleted"); + + b.Property("TotalRacecarBoostsActivated"); + + b.Property("TotalRacecarWrecks"); + + b.Property("TotalRacesFinished"); + + b.Property("TotalRacingImaginationCratesSmashed"); + + b.Property("TotalRacingImaginationPowerUpsCollected"); + + b.Property("TotalRacingSmashablesSmashed"); + + b.Property("TotalRocketsUsed"); + + b.Property("TotalSmashablesSmashed"); + + b.Property("TotalSuicides"); + + b.Property("TotalTimeAirborne"); + + b.Property("UniverseScore"); + + b.Property("UserId"); + + b.HasKey("CharacterId"); + + b.HasIndex("UserId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Uchu.Core.CharacterMail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AttachmentCount"); + + b.Property("AttachmentCurrency"); + + b.Property("AttachmentLot"); + + b.Property("AuthorId"); + + b.Property("Body"); + + b.Property("ExpirationTime"); + + b.Property("Read"); + + b.Property("RecipientId"); + + b.Property("SentTime"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("Mails"); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("FriendId"); + + b.Property("FriendTwoId"); + + b.Property("IsAccepted"); + + b.Property("IsBestFriend"); + + b.Property("IsDeclined"); + + b.Property("RequestHasBeenSent"); + + b.Property("RequestingBestFriend"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("FriendTwoId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("Uchu.Core.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RecipientId"); + + b.Property("SenderId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildInvites"); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.Property("InventoryItemId") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Count"); + + b.Property("ExtraInfo"); + + b.Property("InventoryType"); + + b.Property("IsBound"); + + b.Property("IsEquipped"); + + b.Property("LOT"); + + b.Property("Slot"); + + b.HasKey("InventoryItemId"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItems"); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("CompletionCount"); + + b.Property("LastCompletion"); + + b.Property("MissionId"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("Missions"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionId"); + + b.Property("TaskId"); + + b.HasKey("Id"); + + b.HasIndex("MissionId"); + + b.ToTable("MissionTasks"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("MissionTaskId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("MissionTaskId"); + + b.ToTable("MissionTaskValue"); + }); + + modelBuilder.Entity("Uchu.Core.ServerSpecification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ActiveUserCount"); + + b.Property("MaxUserCount"); + + b.Property("Port"); + + b.Property("ServerType"); + + b.Property("ZoneCloneId"); + + b.Property("ZoneId"); + + b.Property("ZoneInstanceId"); + + b.HasKey("Id"); + + b.ToTable("Specifications"); + }); + + modelBuilder.Entity("Uchu.Core.SessionCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Key"); + + b.Property("UserId"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("SessionCaches"); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("EmoteId"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("UnlockedEmote"); + }); + + modelBuilder.Entity("Uchu.Core.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd(); + + b.Property("Banned"); + + b.Property("BannedReason"); + + b.Property("CharacterIndex"); + + b.Property("CustomLockout"); + + b.Property("FirstTimeOnSubscription"); + + b.Property("FreeToPlay"); + + b.Property("GameMasterLevel"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(60); + + b.Property("Username") + .IsRequired() + .HasMaxLength(33); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Uchu.Core.WorldServerRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("SpecificationId"); + + b.Property("State"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("WorldServerRequests"); + }); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.HasOne("Uchu.Core.User", "User") + .WithMany("Characters") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.HasOne("Uchu.Core.Character", "FriendOne") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Uchu.Core.Character", "FriendTwo") + .WithMany() + .HasForeignKey("FriendTwoId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.HasOne("Uchu.Core.Guild", "Guild") + .WithMany("Invites") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Items") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Missions") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.HasOne("Uchu.Core.Mission", "Mission") + .WithMany("Tasks") + .HasForeignKey("MissionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.HasOne("Uchu.Core.MissionTask", "MissionTask") + .WithMany("Values") + .HasForeignKey("MissionTaskId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("UnlockedEmotes") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.cs b/Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.cs new file mode 100644 index 00000000..3ebd90ac --- /dev/null +++ b/Uchu.Core/Migrations/MySql/20200201151430_MySqlGuild.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Uchu.Core.Migrations.MySql +{ + public partial class MySqlGuild : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GuildId", + table: "Characters", + nullable: false, + defaultValue: 0L); + + migrationBuilder.CreateTable( + name: "Guilds", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + CreatorId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Guilds", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GuildInvites", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + SenderId = table.Column(nullable: false), + RecipientId = table.Column(nullable: false), + GuildId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuildInvites", x => x.Id); + table.ForeignKey( + name: "FK_GuildInvites_Guilds_GuildId", + column: x => x.GuildId, + principalTable: "Guilds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_GuildInvites_GuildId", + table: "GuildInvites", + column: "GuildId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "GuildInvites"); + + migrationBuilder.DropTable( + name: "Guilds"); + + migrationBuilder.DropColumn( + name: "GuildId", + table: "Characters"); + } + } +} diff --git a/Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.Designer.cs b/Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.Designer.cs new file mode 100644 index 00000000..fdce4e67 --- /dev/null +++ b/Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.Designer.cs @@ -0,0 +1,517 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Uchu.Core.Providers; + +namespace Uchu.Core.Migrations +{ + [DbContext(typeof(PostgresContext))] + [Migration("20200201151447_PostgresGuild")] + partial class PostgresGuild + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.Property("CharacterId") + .ValueGeneratedOnAdd(); + + b.Property("BaseHealth"); + + b.Property("BaseImagination"); + + b.Property("Currency"); + + b.Property("CurrentArmor"); + + b.Property("CurrentHealth"); + + b.Property("CurrentImagination"); + + b.Property("CustomName") + .IsRequired() + .HasMaxLength(33); + + b.Property("EyeStyle"); + + b.Property("EyebrowStyle"); + + b.Property("FreeToPlay"); + + b.Property("GuildId"); + + b.Property("HairColor"); + + b.Property("HairStyle"); + + b.Property("InventorySize"); + + b.Property("LandingByRocket"); + + b.Property("LastActivity"); + + b.Property("LastClone"); + + b.Property("LastInstance"); + + b.Property("LastZone"); + + b.Property("LaunchedRocketFrom"); + + b.Property("Level"); + + b.Property("Lh"); + + b.Property("MaximumArmor"); + + b.Property("MaximumHealth"); + + b.Property("MaximumImagination"); + + b.Property("MouthStyle"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(33); + + b.Property("NameRejected"); + + b.Property("PantsColor"); + + b.Property("Rh"); + + b.Property("Rocket") + .HasMaxLength(30); + + b.Property("ShirtColor"); + + b.Property("ShirtStyle"); + + b.Property("TotalArmorPowerUpsCollected"); + + b.Property("TotalArmorRepaired"); + + b.Property("TotalBricksCollected"); + + b.Property("TotalCurrencyCollected"); + + b.Property("TotalDamageHealed"); + + b.Property("TotalDamageTaken"); + + b.Property("TotalDistanceDriven"); + + b.Property("TotalDistanceTraveled"); + + b.Property("TotalEnemiesSmashed"); + + b.Property("TotalFirstPlaceFinishes"); + + b.Property("TotalImaginationPowerUpsCollected"); + + b.Property("TotalImaginationRestored"); + + b.Property("TotalImaginationUsed"); + + b.Property("TotalLifePowerUpsCollected"); + + b.Property("TotalMissionsCompleted"); + + b.Property("TotalPetsTamed"); + + b.Property("TotalQuickBuildsCompleted"); + + b.Property("TotalRacecarBoostsActivated"); + + b.Property("TotalRacecarWrecks"); + + b.Property("TotalRacesFinished"); + + b.Property("TotalRacingImaginationCratesSmashed"); + + b.Property("TotalRacingImaginationPowerUpsCollected"); + + b.Property("TotalRacingSmashablesSmashed"); + + b.Property("TotalRocketsUsed"); + + b.Property("TotalSmashablesSmashed"); + + b.Property("TotalSuicides"); + + b.Property("TotalTimeAirborne"); + + b.Property("UniverseScore"); + + b.Property("UserId"); + + b.HasKey("CharacterId"); + + b.HasIndex("UserId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Uchu.Core.CharacterMail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AttachmentCount"); + + b.Property("AttachmentCurrency") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("AttachmentLot"); + + b.Property("AuthorId"); + + b.Property("Body"); + + b.Property("ExpirationTime"); + + b.Property("Read"); + + b.Property("RecipientId"); + + b.Property("SentTime"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("Mails"); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("FriendId"); + + b.Property("FriendTwoId"); + + b.Property("IsAccepted"); + + b.Property("IsBestFriend"); + + b.Property("IsDeclined"); + + b.Property("RequestHasBeenSent"); + + b.Property("RequestingBestFriend"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("FriendTwoId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("Uchu.Core.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RecipientId"); + + b.Property("SenderId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildInvites"); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.Property("InventoryItemId") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Count"); + + b.Property("ExtraInfo"); + + b.Property("InventoryType"); + + b.Property("IsBound"); + + b.Property("IsEquipped"); + + b.Property("LOT"); + + b.Property("Slot"); + + b.HasKey("InventoryItemId"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItems"); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("CompletionCount"); + + b.Property("LastCompletion"); + + b.Property("MissionId"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("Missions"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionId"); + + b.Property("TaskId"); + + b.HasKey("Id"); + + b.HasIndex("MissionId"); + + b.ToTable("MissionTasks"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("MissionTaskId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("MissionTaskId"); + + b.ToTable("MissionTaskValue"); + }); + + modelBuilder.Entity("Uchu.Core.ServerSpecification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ActiveUserCount"); + + b.Property("MaxUserCount"); + + b.Property("Port"); + + b.Property("ServerType"); + + b.Property("ZoneCloneId"); + + b.Property("ZoneId"); + + b.Property("ZoneInstanceId"); + + b.HasKey("Id"); + + b.ToTable("Specifications"); + }); + + modelBuilder.Entity("Uchu.Core.SessionCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Key"); + + b.Property("UserId"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("SessionCaches"); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("EmoteId"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("UnlockedEmote"); + }); + + modelBuilder.Entity("Uchu.Core.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd(); + + b.Property("Banned"); + + b.Property("BannedReason"); + + b.Property("CharacterIndex"); + + b.Property("CustomLockout"); + + b.Property("FirstTimeOnSubscription"); + + b.Property("FreeToPlay"); + + b.Property("GameMasterLevel"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(60); + + b.Property("Username") + .IsRequired() + .HasMaxLength(33); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Uchu.Core.WorldServerRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("SpecificationId"); + + b.Property("State"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("WorldServerRequests"); + }); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.HasOne("Uchu.Core.User", "User") + .WithMany("Characters") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.HasOne("Uchu.Core.Character", "FriendOne") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Uchu.Core.Character", "FriendTwo") + .WithMany() + .HasForeignKey("FriendTwoId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.HasOne("Uchu.Core.Guild", "Guild") + .WithMany("Invites") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Items") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Missions") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.HasOne("Uchu.Core.Mission", "Mission") + .WithMany("Tasks") + .HasForeignKey("MissionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.HasOne("Uchu.Core.MissionTask", "MissionTask") + .WithMany("Values") + .HasForeignKey("MissionTaskId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("UnlockedEmotes") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.cs b/Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.cs new file mode 100644 index 00000000..1ceb2166 --- /dev/null +++ b/Uchu.Core/Migrations/Postgres/20200201151447_PostgresGuild.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Uchu.Core.Migrations +{ + public partial class PostgresGuild : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GuildId", + table: "Characters", + nullable: false, + defaultValue: 0L); + + migrationBuilder.CreateTable( + name: "Guilds", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn), + Name = table.Column(nullable: true), + CreatorId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Guilds", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GuildInvites", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn), + SenderId = table.Column(nullable: false), + RecipientId = table.Column(nullable: false), + GuildId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuildInvites", x => x.Id); + table.ForeignKey( + name: "FK_GuildInvites_Guilds_GuildId", + column: x => x.GuildId, + principalTable: "Guilds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_GuildInvites_GuildId", + table: "GuildInvites", + column: "GuildId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "GuildInvites"); + + migrationBuilder.DropTable( + name: "Guilds"); + + migrationBuilder.DropColumn( + name: "GuildId", + table: "Characters"); + } + } +} From 341d4209bb6f38c135660eeda118853543d0e29b Mon Sep 17 00:00:00 2001 From: wincent Date: Sun, 9 Feb 2020 19:04:29 +0100 Subject: [PATCH 10/23] Accounting for player ping when calculating enemy skills/behaviors. You can now more reliably dodge projectiles. --- Uchu.World/Behaviors/BasicAttackBehavior.cs | 2 + .../Behaviors/ProjectileAttackBehavior.cs | 14 ++++++ Uchu.World/Extensions/GameObjectExtensions.cs | 22 +++++++++ .../Commands/OperatorCommandHandler.cs | 17 +++++++ .../BaseCombatAIComponent.cs | 4 +- .../Components/Server/SpawnerComponent.cs | 4 ++ .../Objects/GameObjects/InstancingUtil.cs | 6 +-- Uchu.World/Objects/GameObjects/Player.cs | 2 + Uchu.World/Objects/GameObjects/Projectile.cs | 19 ++++++- Uchu.World/Objects/Zone.cs | 6 ++- Uchu.World/Social/UiHelper.cs | 49 +++++++++++++++++++ 11 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 Uchu.World/Extensions/GameObjectExtensions.cs diff --git a/Uchu.World/Behaviors/BasicAttackBehavior.cs b/Uchu.World/Behaviors/BasicAttackBehavior.cs index d3490574..f1be312c 100644 --- a/Uchu.World/Behaviors/BasicAttackBehavior.cs +++ b/Uchu.World/Behaviors/BasicAttackBehavior.cs @@ -60,6 +60,8 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) { + await branchContext.Target.NetFavorAsync(); + Logger.Debug($"NPC is attacking: {branchContext.Target}"); context.Writer.Align(); diff --git a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs index a68354ac..a6f2ed46 100644 --- a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs +++ b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs @@ -13,6 +13,10 @@ public class ProjectileAttackBehavior : BehaviorBase public Lot ProjectileLot { get; set; } public float ProjectileSpeed { get; set; } + + public float MaxDistance { get; set; } + + public float TrackRadius { get; set; } public override async Task BuildAsync() { @@ -21,6 +25,10 @@ public override async Task BuildAsync() ProjectileLot = await GetParameter("LOT_ID"); ProjectileSpeed = await GetParameter("projectile_speed"); + + MaxDistance = await GetParameter("max_distance"); + + TrackRadius = await GetParameter("track_radius"); // ??? } public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) @@ -65,6 +73,9 @@ private void CalculateProjectile(ExecutionContext context, GameObject target) projectile.ClientObjectId = projectileId; projectile.Target = target; projectile.Lot = ProjectileLot; + projectile.Destination = target.Transform.Position; + projectile.RadiusCheck = TrackRadius; + projectile.MaxDistance = MaxDistance; Object.Start(projectile); @@ -92,6 +103,9 @@ private void StartProjectile(ExecutionContext context, GameObject target) projectile.ClientObjectId = projectileId; projectile.Target = target; projectile.Lot = ProjectileLot; + projectile.Destination = target.Transform.Position; + projectile.RadiusCheck = TrackRadius; + projectile.MaxDistance = MaxDistance; ((Player) context.Associate)?.SendChatMessage($"Start PROJ: [{projectile.Lot}] {projectile.ClientObjectId} -> {projectile.Target}"); diff --git a/Uchu.World/Extensions/GameObjectExtensions.cs b/Uchu.World/Extensions/GameObjectExtensions.cs new file mode 100644 index 00000000..26c48a15 --- /dev/null +++ b/Uchu.World/Extensions/GameObjectExtensions.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace Uchu.World +{ + public static class GameObjectExtensions + { + /// + /// Accounts for network delay, e.g, player ping. + /// + /// Networked GameObject + /// Task to be awaited to account for ping + public static Task NetFavorAsync(this GameObject @this) + { + if (@this is Player player) + { + return Task.Delay(player.Ping); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs index 590a4a02..25f9caaf 100644 --- a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; @@ -176,5 +177,21 @@ public async Task Toggle(string[] arguments, Player player) return "Sent ui message."; } + + [CommandHandler(Signature = "imagination", Help = "Send imagination ui message", GameMasterLevel = GameMasterLevel.Admin)] + public async Task Imagination(string[] arguments, Player player) + { + var current = uint.Parse(arguments[0]); + + var max = uint.Parse(arguments[1]); + + await player.MessageGuiAsync("SetImagination", new Dictionary() + { + {"imaginationMax", max}, + {"imagination", current} + }); + + return "Sent"; + } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index 463636a9..cbdcb205 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -45,7 +45,7 @@ public BaseCombatAiComponent() await using var ctx = new CdClientContext(); - Cooldown = 0.5f; + Cooldown = 1f; foreach (var skillId in skillComponent.DefaultSkillSet) { @@ -73,7 +73,7 @@ public BaseCombatAiComponent() } }); - Cooldown = (skillInfo.Cooldown ?? 0.5f) + time; + Cooldown = (skillInfo.Cooldown ?? 1f) + time; break; } diff --git a/Uchu.World/Objects/Components/Server/SpawnerComponent.cs b/Uchu.World/Objects/Components/Server/SpawnerComponent.cs index 7631ce68..0e2baaba 100644 --- a/Uchu.World/Objects/Components/Server/SpawnerComponent.cs +++ b/Uchu.World/Objects/Components/Server/SpawnerComponent.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Numerics; +using InfectedRose.Luz; using InfectedRose.Lvl; namespace Uchu.World @@ -9,6 +11,8 @@ public class SpawnerComponent : Component public List ActiveSpawns { get; } public LevelObjectTemplate LevelObject { get; set; } + + public LuzSpawnerWaypoint[] SpawnLocations { get; set; } protected SpawnerComponent() { diff --git a/Uchu.World/Objects/GameObjects/InstancingUtil.cs b/Uchu.World/Objects/GameObjects/InstancingUtil.cs index 2629b175..9af3c3d9 100644 --- a/Uchu.World/Objects/GameObjects/InstancingUtil.cs +++ b/Uchu.World/Objects/GameObjects/InstancingUtil.cs @@ -46,9 +46,9 @@ public static GameObject Spawner(LevelObjectTemplate levelObject, Object parent) public static GameObject Spawner(LuzSpawnerPath spawnerPath, Object parent) { - if (!spawnerPath.Waypoints.Any()) return null; - - var wayPoint = (LuzSpawnerWaypoint) spawnerPath.Waypoints[0]; + if (spawnerPath.Waypoints.Length == default) return default; + + var wayPoint = (LuzSpawnerWaypoint) spawnerPath.Waypoints[default]; var spawner = GameObject.Instantiate( parent, diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index 7f2d709d..1641770f 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -74,6 +74,8 @@ private Player() public GuildGuiState GuildGuiState { get; set; } public string GuildInviteName { get; set; } + + public int Ping => Connection.AveragePing; public override string Name { diff --git a/Uchu.World/Objects/GameObjects/Projectile.cs b/Uchu.World/Objects/GameObjects/Projectile.cs index 13ccb782..20aed22c 100644 --- a/Uchu.World/Objects/GameObjects/Projectile.cs +++ b/Uchu.World/Objects/GameObjects/Projectile.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using System.Numerics; using System.Threading.Tasks; using RakDotNet.IO; using Uchu.World.Behaviors; @@ -15,9 +16,21 @@ public class Projectile : Object public GameObject Owner { get; set; } public GameObject Target { get; set; } + + public Vector3 Destination { get; set; } + + public float MaxDistance { get; set; } + + public float RadiusCheck { get; set; } public async Task ImpactAsync(byte[] data, GameObject target) { + await target.NetFavorAsync(); + + var distance = Vector3.Distance(Destination, target.Transform.Position); + + if (distance > RadiusCheck) return; + target ??= Target; var tree = new BehaviorTree(Lot); @@ -46,7 +59,11 @@ public async Task ImpactAsync(byte[] data, GameObject target) public async Task CalculateImpactAsync(GameObject target) { - target ??= Target; + await target.NetFavorAsync(); + + var distance = Vector3.Distance(Destination, target.Transform.Position); + + if (distance > RadiusCheck) return; var tree = new BehaviorTree(Lot); diff --git a/Uchu.World/Objects/Zone.cs b/Uchu.World/Objects/Zone.cs index 335da3f8..08319098 100644 --- a/Uchu.World/Objects/Zone.cs +++ b/Uchu.World/Objects/Zone.cs @@ -204,8 +204,12 @@ private void SpawnPath(LuzSpawnerPath spawnerPath) obj.Layer = StandardLayer.Hidden; Start(obj); + + var spawner = obj.GetComponent(); + + spawner.SpawnLocations = spawnerPath.Waypoints.Select(w => (LuzSpawnerWaypoint) w).ToArray(); - var spawn = obj.GetComponent().Spawn(); + var spawn = spawner.Spawn(); GameObject.Construct(spawn); } diff --git a/Uchu.World/Social/UiHelper.cs b/Uchu.World/Social/UiHelper.cs index b66c0236..3f8bf8ff 100644 --- a/Uchu.World/Social/UiHelper.cs +++ b/Uchu.World/Social/UiHelper.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using RakDotNet.IO; @@ -158,6 +160,53 @@ public static async Task SingleArgumentGuiAsync(this Player @this, string name, }); } + public static async Task MessageGuiAsync(this Player @this, string name, IDictionary arguments) + { + if (arguments == null) + { + throw new ArgumentNullException(nameof(arguments)); + } + + await using var stream = new MemoryStream(); + using var writer = new BitWriter(stream); + + writer.Write((byte) Amf3Type.Array); + writer.Write(1); + + foreach (var (key, value) in arguments) + { + Amf3Helper.WriteText(writer, key); + + switch (value) + { + case string str: + writer.Write((byte) Amf3Type.String); + Amf3Helper.WriteText(writer, str); + break; + case int integer: + writer.Write((byte) Amf3Type.Integer); + Amf3Helper.WriteNumber2(writer, (uint) integer); + break; + case uint unsigned: + writer.Write((byte) Amf3Type.Integer); + Amf3Helper.WriteNumber2(writer, unsigned); + break; + case null: + writer.Write((byte) Amf3Type.Undefined); + break; + } + } + + writer.Write((byte) Amf3Type.Null); + + @this.Message(new UiMessageServerToSingleClientMessage + { + Associate = @this, + Content = stream.ToArray(), + MessageName = name + }); + } + public static async Task MessageGuiAsync(this Player @this, string name) { await using var stream = new MemoryStream(); From 84fdbe293534d5a5f2507dbc94eef2de326d96d6 Mon Sep 17 00:00:00 2001 From: wincent Date: Sun, 9 Feb 2020 20:33:27 +0100 Subject: [PATCH 11/23] Enemy skill execution: * Added area of affect calculation. * With AOE, most skills can now be calculated. * Skill individual cooldown. * Enemies will now use all of their skills. --- Uchu.World/AI/NpcSkillEntry.cs | 9 +++ Uchu.World/Behaviors/AreaOfEffect.cs | 53 +++++++++++++++ Uchu.World/Behaviors/BehaviorTree.cs | 5 +- Uchu.World/Behaviors/NpcExecutionContext.cs | 10 ++- .../Behaviors/ProjectileAttackBehavior.cs | 5 ++ Uchu.World/Behaviors/TacArcBehavior.cs | 2 +- .../BaseCombatAIComponent.cs | 68 +++++++++++-------- .../ReplicaComponents/SkillComponent.cs | 2 +- .../Objects/GameObjects/InstancingUtil.cs | 39 ++++++----- Uchu.World/Objects/GameObjects/Projectile.cs | 1 + 10 files changed, 144 insertions(+), 50 deletions(-) create mode 100644 Uchu.World/AI/NpcSkillEntry.cs diff --git a/Uchu.World/AI/NpcSkillEntry.cs b/Uchu.World/AI/NpcSkillEntry.cs new file mode 100644 index 00000000..a17ed806 --- /dev/null +++ b/Uchu.World/AI/NpcSkillEntry.cs @@ -0,0 +1,9 @@ +namespace Uchu.World.AI +{ + public class NpcSkillEntry + { + public uint SkillId { get; set; } + + public bool Cooldown { get; set; } + } +} \ No newline at end of file diff --git a/Uchu.World/Behaviors/AreaOfEffect.cs b/Uchu.World/Behaviors/AreaOfEffect.cs index 527099de..d9a1e603 100644 --- a/Uchu.World/Behaviors/AreaOfEffect.cs +++ b/Uchu.World/Behaviors/AreaOfEffect.cs @@ -1,3 +1,5 @@ +using System.Linq; +using System.Numerics; using System.Threading.Tasks; namespace Uchu.World.Behaviors @@ -8,9 +10,17 @@ public class AreaOfEffect : BehaviorBase public BehaviorBase Action { get; set; } + public int MaxTargets { get; set; } + + public float Radius { get; set; } + public override async Task BuildAsync() { Action = await GetBehavior("action"); + + MaxTargets = await GetParameter("max targets"); + + Radius = await GetParameter("radius"); } public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) @@ -41,5 +51,48 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc await Action.ExecuteAsync(context, new ExecutionBranchContext(target)); } } + + public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) + { + if (!context.Associate.TryGetComponent(out var baseCombatAiComponent)) return; + + var validTarget = await baseCombatAiComponent.SeekValidTargetsAsync(); + + var sourcePosition = context.CalculatingPosition; + + var targets = validTarget.Where(target => + { + var transform = target.Transform; + + var distance = Vector3.Distance(transform.Position, sourcePosition); + + var valid = distance <= Radius; + + return valid; + }).ToArray(); + + foreach (var target in targets) + { + if (target is Player player) + { + player.SendChatMessage("You are a AOE target!"); + } + } + + if (targets.Length > 0) + context.FoundTarget = true; + + context.Writer.Write((uint) targets.Length); + + foreach (var target in targets) + { + context.Writer.Write(target); + } + + foreach (var target in targets) + { + await Action.CalculateAsync(context, new ExecutionBranchContext(target)); + } + } } } \ No newline at end of file diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 5b310a6b..316ac651 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using RakDotNet.IO; @@ -162,11 +163,11 @@ public async Task BuildAsync() /// Sync Id /// Explicit target /// Context - public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, uint syncId, GameObject target = default) + public async Task CalculateAsync(GameObject associate, BitWriter writer, int skillId, uint syncId, Vector3 calculatingPosition, GameObject target = default) { target ??= associate; - var context = new NpcExecutionContext(target, writer, skillId, syncId); + var context = new NpcExecutionContext(target, writer, skillId, syncId, calculatingPosition); if (!SkillRoots.TryGetValue(skillId, out var root)) { diff --git a/Uchu.World/Behaviors/NpcExecutionContext.cs b/Uchu.World/Behaviors/NpcExecutionContext.cs index 65bbf84e..53ccb32d 100644 --- a/Uchu.World/Behaviors/NpcExecutionContext.cs +++ b/Uchu.World/Behaviors/NpcExecutionContext.cs @@ -17,6 +17,8 @@ public class NpcExecutionContext : ExecutionContext public uint SkillSyncId { get; set; } public bool FoundTarget { get; set; } + + public Vector3 CalculatingPosition { get; set; } public bool Alive { @@ -34,8 +36,10 @@ public bool Alive } } - public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId, uint skillSyncId) : base(associate, default, writer) + public NpcExecutionContext(GameObject associate, BitWriter writer, int skillId, uint skillSyncId, Vector3 calculatingPosition) + : base(associate, default, writer) { + CalculatingPosition = calculatingPosition; SkillId = skillId; SkillSyncId = skillSyncId; } @@ -54,7 +58,7 @@ public void Sync(uint behaviorSyncId) public NpcExecutionContext Copy() { - return new NpcExecutionContext(Associate, new BitWriter(new MemoryStream()), SkillId, SkillSyncId) + return new NpcExecutionContext(Associate, new BitWriter(new MemoryStream()), SkillId, SkillSyncId, CalculatingPosition) { MaxRange = MaxRange, MinRange = MinRange @@ -63,6 +67,8 @@ public NpcExecutionContext Copy() public bool IsValidTarget(GameObject gameObject) { + if (MaxRange.Equals(0)) return true; + var distance = Vector3.Distance(gameObject.Transform.Position, Associate.Transform.Position); return distance <= MaxRange; diff --git a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs index a6f2ed46..6a45276b 100644 --- a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs +++ b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs @@ -63,6 +63,11 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution private void CalculateProjectile(ExecutionContext context, GameObject target) { + if (target is Player player) + { + player.SendChatMessage("You are a projectile target!"); + } + var projectileId = IdUtilities.GenerateObjectId(); context.Writer.Write(projectileId); diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 21071153..1c48e53c 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -114,7 +114,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution var validTarget = await baseCombatAiComponent.SeekValidTargetsAsync(); - var sourcePosition = context.Associate.Transform.Position; + var sourcePosition = context.CalculatingPosition; // Change back to author position? var targets = validTarget.Where(target => { diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index cbdcb205..5489a640 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -1,8 +1,10 @@ +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using RakDotNet.IO; using Uchu.Core.Client; +using Uchu.World.AI; namespace Uchu.World { @@ -18,62 +20,72 @@ public class BaseCombatAiComponent : ReplicaComponent public float Cooldown { get; set; } + public List SkillEntries { get; set; } + + private SkillComponent SkillComponent { get; set; } + + private DestructibleComponent DestructibleComponent { get; set; } + + private QuickBuildComponent QuickBuildComponent { get; set; } + public BaseCombatAiComponent() { - var idle = true; - - Listen(OnTick, async () => + Listen(OnStart, async () => { - var skillComponent = GameObject.GetComponent(); + SkillEntries = new List(); + + SkillComponent = GameObject.GetComponent(); + + DestructibleComponent = GameObject.GetComponent(); - var destructComponent = GameObject.GetComponent(); + QuickBuildComponent = GameObject.GetComponent(); - var rebuild = GameObject.GetComponent(); + foreach (var skillId in SkillComponent.DefaultSkillSet) + { + SkillEntries.Add(new NpcSkillEntry + { + SkillId = skillId, + Cooldown = false + }); + } + }); + + Listen(OnTick, async () => + { + if (GameObject.Lot == 6007 || GameObject.Lot == 6366) return; // TODO: Remove - if (!destructComponent.Alive) return; + if (!DestructibleComponent.Alive) return; - if (rebuild != default && rebuild.State != RebuildState.Completed) return; + if (QuickBuildComponent != default && QuickBuildComponent.State != RebuildState.Completed) return; if (Cooldown <= 0) { - if (!idle) - { - Action = CombatAiAction.Idle; - - idle = true; - } - await using var ctx = new CdClientContext(); Cooldown = 1f; - foreach (var skillId in skillComponent.DefaultSkillSet) + foreach (var entry in SkillEntries.Where(s => !s.Cooldown)) { - var time = await skillComponent.CalculateSkillAsync((int) skillId); + var time = await SkillComponent.CalculateSkillAsync((int) entry.SkillId); if (time.Equals(0)) continue; - Action = CombatAiAction.Attacking; - - idle = false; + entry.Cooldown = true; var skillInfo = await ctx.SkillBehaviorTable.FirstAsync( - s => s.SkillID == skillId + s => s.SkillID == entry.SkillId ); var _ = Task.Run(async () => { - await Task.Delay((int) (time * 1000)); + var cooldown = (skillInfo.Cooldown ?? 1f) + time; - if (!PerformingAction) - { - Action = CombatAiAction.Idle; + await Task.Delay((int) cooldown * 1000); - idle = true; - } + entry.Cooldown = false; }); - Cooldown = (skillInfo.Cooldown ?? 1f) + time; + Cooldown += time; break; } diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index fa20da24..c6d8257a 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -168,7 +168,7 @@ public async Task CalculateSkillAsync(int skillId) var syncId = ClaimSyncId(); - var context = await tree.CalculateAsync(GameObject, writer, skillId, syncId); + var context = await tree.CalculateAsync(GameObject, writer, skillId, syncId, Transform.Position); if (!context.FoundTarget) return 0; diff --git a/Uchu.World/Objects/GameObjects/InstancingUtil.cs b/Uchu.World/Objects/GameObjects/InstancingUtil.cs index 9af3c3d9..60da9c33 100644 --- a/Uchu.World/Objects/GameObjects/InstancingUtil.cs +++ b/Uchu.World/Objects/GameObjects/InstancingUtil.cs @@ -119,24 +119,31 @@ public static GameObject Loot(Lot lot, Player owner, GameObject source, Vector3 public static void Currency(int currency, Player owner, GameObject source, Vector3 spawn) { - var finalPosition = new Vector3 + try { - X = spawn.X + ((float) Random.NextDouble() % 1f - 0.5f) * 20f, - Y = spawn.Y, - Z = spawn.Z + ((float) Random.NextDouble() % 1f - 0.5f) * 20f - }; - - owner.Message(new DropClientLootMessage + var finalPosition = new Vector3 + { + X = spawn.X + ((float) Random.NextDouble() % 1f - 0.5f) * 20f, + Y = spawn.Y, + Z = spawn.Z + ((float) Random.NextDouble() % 1f - 0.5f) * 20f + }; + + owner.Message(new DropClientLootMessage + { + Associate = owner, + Currency = currency, + Owner = owner, + Source = source, + SpawnPosition = spawn + Vector3.UnitY, + FinalPosition = finalPosition + }); + + owner.EntitledCurrency += currency; + } + catch (Exception e) { - Associate = owner, - Currency = currency, - Owner = owner, - Source = source, - SpawnPosition = spawn + Vector3.UnitY, - FinalPosition = finalPosition - }); - - owner.EntitledCurrency += currency; + Logger.Error(e); + } } } } \ No newline at end of file diff --git a/Uchu.World/Objects/GameObjects/Projectile.cs b/Uchu.World/Objects/GameObjects/Projectile.cs index 20aed22c..f6ebfd64 100644 --- a/Uchu.World/Objects/GameObjects/Projectile.cs +++ b/Uchu.World/Objects/GameObjects/Projectile.cs @@ -78,6 +78,7 @@ await tree.CalculateAsync( writer, tree.SkillRoots.First().Key, Owner.GetComponent().ClaimSyncId(), + target.Transform.Position, target ); From 7684ff8504edea890a5ef16b365a00634ce8c29d Mon Sep 17 00:00:00 2001 From: wincent Date: Sun, 9 Feb 2020 22:07:48 +0100 Subject: [PATCH 12/23] Faction calculations are now done to select correct targets. --- Uchu.World/Behaviors/AreaOfEffect.cs | 2 +- Uchu.World/Behaviors/TacArcBehavior.cs | 2 +- .../BaseCombatAIComponent.cs | 29 +++++++-- Uchu.World/Objects/Components/Stats.cs | 20 +++++- .../Objects/GameObjects/InstancingUtil.cs | 61 +++++++++++-------- Uchu.World/Objects/Object.cs | 3 +- 6 files changed, 82 insertions(+), 35 deletions(-) diff --git a/Uchu.World/Behaviors/AreaOfEffect.cs b/Uchu.World/Behaviors/AreaOfEffect.cs index d9a1e603..89aac134 100644 --- a/Uchu.World/Behaviors/AreaOfEffect.cs +++ b/Uchu.World/Behaviors/AreaOfEffect.cs @@ -56,7 +56,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution { if (!context.Associate.TryGetComponent(out var baseCombatAiComponent)) return; - var validTarget = await baseCombatAiComponent.SeekValidTargetsAsync(); + var validTarget = baseCombatAiComponent.SeekValidTargets(); var sourcePosition = context.CalculatingPosition; diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index 1c48e53c..bffe5806 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -112,7 +112,7 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution { if (!context.Associate.TryGetComponent(out var baseCombatAiComponent)) return; - var validTarget = await baseCombatAiComponent.SeekValidTargetsAsync(); + var validTarget = baseCombatAiComponent.SeekValidTargets(); var sourcePosition = context.CalculatingPosition; // Change back to author position? diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index 5489a640..918853ef 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.Scripting.Utils; using RakDotNet.IO; using Uchu.Core.Client; using Uchu.World.AI; @@ -28,9 +29,11 @@ public class BaseCombatAiComponent : ReplicaComponent private QuickBuildComponent QuickBuildComponent { get; set; } + private Stats Stats { get; set; } + public BaseCombatAiComponent() { - Listen(OnStart, async () => + Listen(OnStart, () => { SkillEntries = new List(); @@ -40,6 +43,8 @@ public BaseCombatAiComponent() QuickBuildComponent = GameObject.GetComponent(); + Stats = GameObject.GetComponent(); + foreach (var skillId in SkillComponent.DefaultSkillSet) { SkillEntries.Add(new NpcSkillEntry @@ -52,8 +57,6 @@ public BaseCombatAiComponent() Listen(OnTick, async () => { - if (GameObject.Lot == 6007 || GameObject.Lot == 6366) return; // TODO: Remove - if (!DestructibleComponent.Alive) return; if (QuickBuildComponent != default && QuickBuildComponent.State != RebuildState.Completed) return; @@ -110,11 +113,25 @@ public override void Serialize(BitWriter writer) writer.Write(Target); } - public async Task SeekValidTargetsAsync() + public GameObject[] SeekValidTargets() { - // TODO: Do faction calculations + // TODO: Optimize + + if (Stats.Factions.Length == default) return new GameObject[0]; + + var entries = Zone.Objects.OfType(); + + var targets = new List(); + + foreach (var entry in entries.Where(e => e.Factions.Length != default && e.Health > 0)) + { + if (Stats.Enemies.Contains(entry.Factions.First())) + { + targets.Add(entry.GameObject); + } + } - return Zone.Players.ToArray(); + return targets.ToArray(); } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/Stats.cs b/Uchu.World/Objects/Components/Stats.cs index 798afe1a..2065555f 100644 --- a/Uchu.World/Objects/Components/Stats.cs +++ b/Uchu.World/Objects/Components/Stats.cs @@ -25,6 +25,8 @@ public class Stats : Component public int[] Factions { get; set; } = new int[0]; + public int[] Enemies { get; set; } = new int[0]; + public uint DamageAbsorptionPoints { get; set; } public bool Immune { get; set; } @@ -192,12 +194,12 @@ public uint MaxImagination protected Stats() { - Listen(OnStart, () => + Listen(OnStart, async () => { if (GameObject is Player) CollectPlayerStats(); else CollectObjectStats(); - using var cdClient = new CdClientContext(); + await using var cdClient = new CdClientContext(); var destroyable = cdClient.DestructibleComponentTable.FirstOrDefault( c => c.Id == GameObject.Lot.GetComponentId(ComponentId.DestructibleComponent) @@ -206,6 +208,20 @@ protected Stats() if (destroyable == default) return; Factions = new[] {destroyable.Faction ?? 1}; + + var faction = await cdClient.FactionsTable.FirstOrDefaultAsync( + f => f.Faction == Factions[0] + ); + + if (faction?.EnemyList == default) return; + + if (string.IsNullOrWhiteSpace(faction.EnemyList)) return; + + Enemies = faction.EnemyList + .Replace(" ", "") + .Split(',') + .Select(int.Parse) + .ToArray(); }); Listen(OnDestroyed, () => diff --git a/Uchu.World/Objects/GameObjects/InstancingUtil.cs b/Uchu.World/Objects/GameObjects/InstancingUtil.cs index 60da9c33..03005914 100644 --- a/Uchu.World/Objects/GameObjects/InstancingUtil.cs +++ b/Uchu.World/Objects/GameObjects/InstancingUtil.cs @@ -87,38 +87,51 @@ public static GameObject Spawner(LuzSpawnerPath spawnerPath, Object parent) public static GameObject Loot(Lot lot, Player owner, GameObject source, Vector3 spawn) { - var drop = GameObject.Instantiate( - owner.Zone, - lot, - spawn - ); - - drop.Layer = StandardLayer.Hidden; + if (owner is null) return default; - var finalPosition = new Vector3 + try { - X = spawn.X + ((float) Random.NextDouble() % 1f - 0.5f) * 20f, - Y = spawn.Y, - Z = spawn.Z + ((float) Random.NextDouble() % 1f - 0.5f) * 20f - }; + var drop = GameObject.Instantiate( + owner.Zone, + lot, + spawn + ); + + drop.Layer = StandardLayer.Hidden; + + var finalPosition = new Vector3 + { + X = spawn.X + ((float) Random.NextDouble() % 1f - 0.5f) * 20f, + Y = spawn.Y, + Z = spawn.Z + ((float) Random.NextDouble() % 1f - 0.5f) * 20f + }; - owner.Message(new DropClientLootMessage + owner.Message(new DropClientLootMessage + { + Associate = owner, + Currency = 0, + Lot = drop.Lot, + Loot = drop, + Owner = owner, + Source = source, + SpawnPosition = drop.Transform.Position + Vector3.UnitY, + FinalPosition = finalPosition + }); + + return drop; + } + catch (Exception e) { - Associate = owner, - Currency = 0, - Lot = drop.Lot, - Loot = drop, - Owner = owner, - Source = source, - SpawnPosition = drop.Transform.Position + Vector3.UnitY, - FinalPosition = finalPosition - }); - - return drop; + Logger.Error(e); + + return default; + } } public static void Currency(int currency, Player owner, GameObject source, Vector3 spawn) { + if (owner is null) return; + try { var finalPosition = new Vector3 diff --git a/Uchu.World/Objects/Object.cs b/Uchu.World/Objects/Object.cs index 1fec9dad..d14423c7 100644 --- a/Uchu.World/Objects/Object.cs +++ b/Uchu.World/Objects/Object.cs @@ -46,7 +46,8 @@ public static Object Instantiate(Zone zone) public static void Start(Object obj) { - if (obj._started) return; + if (obj?._started ?? true) return; + obj._started = true; obj.Zone.RegisterObject(obj); From 973a11613b25c597d8212c182900fec975e10053 Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Mon, 10 Feb 2020 08:20:12 +0100 Subject: [PATCH 13/23] Speed improvements --- .../Commands/OperatorCommandHandler.cs | 6 +++++ .../Player/InventoryManagerComponent.cs | 2 +- Uchu.World/Objects/GameObjects/Player.cs | 24 +++++++++++++++++++ Uchu.World/Objects/Zone.cs | 23 ------------------ 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs index 25f9caaf..c9316f41 100644 --- a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs @@ -193,5 +193,11 @@ public async Task Imagination(string[] arguments, Player player) return "Sent"; } + + [CommandHandler(Signature = "ping", Help = "Get you average ping", GameMasterLevel = GameMasterLevel.Player)] + public string Ping(string[] arguments, Player player) + { + return $"{player.Ping}ms"; + } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/Player/InventoryManagerComponent.cs b/Uchu.World/Objects/Components/Player/InventoryManagerComponent.cs index 77b58b6b..3b9a5110 100644 --- a/Uchu.World/Objects/Components/Player/InventoryManagerComponent.cs +++ b/Uchu.World/Objects/Components/Player/InventoryManagerComponent.cs @@ -185,7 +185,7 @@ public async Task AddItemAsync(int lot, uint count, InventoryType inventoryType, return; } - As().SendChatMessage($"Calculating for {lot} x {count} [{inventoryType}]", PlayerChatChannel.Normal); + As().SendChatMessage($"Calculating for {lot} x {count} [{inventoryType}]"); var stackSize = component.StackSize ?? 1; diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index 1641770f..6e33bf9f 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -54,6 +54,30 @@ private Player() OnWorldLoad.Clear(); OnPositionUpdate.Clear(); }); + + Listen(OnPositionUpdate, (position, rotation) => + { + foreach (var gameObject in Zone.GameObjects) + { + var spawned = Perspective.LoadedObjects.ToArray().Contains(gameObject); + + var view = Perspective.View(gameObject); + + if (spawned && !view) + { + Zone.SendDestruction(gameObject, this); + + continue; + } + + if (!spawned && view) + { + Zone.SendConstruction(gameObject, this); + } + } + + return Task.CompletedTask; + }); } public AsyncEventDictionary OnFireServerEvent { get; } = diff --git a/Uchu.World/Objects/Zone.cs b/Uchu.World/Objects/Zone.cs index 08319098..64c978c2 100644 --- a/Uchu.World/Objects/Zone.cs +++ b/Uchu.World/Objects/Zone.cs @@ -474,29 +474,6 @@ private Task ExecuteUpdateAsync() try { Update(obj); - - if (!(obj is GameObject gameObject)) continue; - - if (obj is Item) continue; - - foreach (var player in players) - { - var spawned = player.Perspective.LoadedObjects.ToArray().Contains(gameObject); - - var view = player.Perspective.View(gameObject); - - if (spawned && !view) - { - SendDestruction(gameObject, player); - - continue; - } - - if (!spawned && view) - { - SendConstruction(gameObject, player); - } - } } catch (Exception e) { From b37281412ed69208a7385dcd3f3d53e10eb75463 Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Mon, 10 Feb 2020 11:03:59 +0100 Subject: [PATCH 14/23] Npcs will now look at their targets. --- Uchu.World/Behaviors/BasicAttackBehavior.cs | 4 +- .../Behaviors/ProjectileAttackBehavior.cs | 2 + Uchu.World/Extensions/MathExtensions.cs | 80 +++++++++++++++++++ Uchu.World/Objects/Components/Transform.cs | 23 ++++++ .../Server/OrientToPositionMessage.cs | 17 ++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 Uchu.World/Packets/GameMessages/Server/OrientToPositionMessage.cs diff --git a/Uchu.World/Behaviors/BasicAttackBehavior.cs b/Uchu.World/Behaviors/BasicAttackBehavior.cs index f1be312c..286a3bdc 100644 --- a/Uchu.World/Behaviors/BasicAttackBehavior.cs +++ b/Uchu.World/Behaviors/BasicAttackBehavior.cs @@ -60,10 +60,12 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc public override async Task CalculateAsync(NpcExecutionContext context, ExecutionBranchContext branchContext) { + context.Associate.Transform.LookAt(branchContext.Target.Transform.Position); + await branchContext.Target.NetFavorAsync(); Logger.Debug($"NPC is attacking: {branchContext.Target}"); - + context.Writer.Align(); context.Writer.Write(0); diff --git a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs index 6a45276b..cd3b7bf1 100644 --- a/Uchu.World/Behaviors/ProjectileAttackBehavior.cs +++ b/Uchu.World/Behaviors/ProjectileAttackBehavior.cs @@ -63,6 +63,8 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution private void CalculateProjectile(ExecutionContext context, GameObject target) { + context.Associate.Transform.LookAt(target.Transform.Position); + if (target is Player player) { player.SendChatMessage("You are a projectile target!"); diff --git a/Uchu.World/Extensions/MathExtensions.cs b/Uchu.World/Extensions/MathExtensions.cs index 483acea9..c84e5f22 100644 --- a/Uchu.World/Extensions/MathExtensions.cs +++ b/Uchu.World/Extensions/MathExtensions.cs @@ -92,5 +92,85 @@ public static Vector3 MoveTowards(this Vector3 current, Vector3 target, float ma current.Z + deltaZ / change * maxDistanceDelta ); } + + public static Quaternion QuaternionLookRotation(this Vector3 forward, Vector3 up) + { + forward = Vector3.Normalize(forward); + + var vector = Vector3.Normalize(forward); + var vector2 = Vector3.Normalize(Vector3.Cross(up, vector)); + var vector3 = Vector3.Cross(vector, vector2); + var m00 = vector2.X; + var m01 = vector2.Y; + var m02 = vector2.Z; + var m10 = vector3.X; + var m11 = vector3.Y; + var m12 = vector3.Z; + var m20 = vector.X; + var m21 = vector.Y; + var m22 = vector.Z; + + + var num8 = m00 + m11 + m22; + var quaternion = new Quaternion(); + if (num8 > 0f) + { + var num = (float)Math.Sqrt(num8 + 1f); + quaternion.W = num * 0.5f; + num = 0.5f / num; + quaternion.X = (m12 - m21) * num; + quaternion.Y = (m20 - m02) * num; + quaternion.Z = (m01 - m10) * num; + return quaternion; + } + if (m00 >= m11 && m00 >= m22) + { + var num7 = (float)Math.Sqrt(1f + m00 - m11 - m22); + var num4 = 0.5f / num7; + quaternion.X = 0.5f * num7; + quaternion.Y = (m01 + m10) * num4; + quaternion.Z = (m02 + m20) * num4; + quaternion.W = (m12 - m21) * num4; + return quaternion; + } + if (m11 > m22) + { + var num6 = (float)Math.Sqrt(1f + m11 - m00 - m22); + var num3 = 0.5f / num6; + quaternion.X = (m10+ m01) * num3; + quaternion.Y = 0.5f * num6; + quaternion.Z = (m21 + m12) * num3; + quaternion.W = (m20 - m02) * num3; + return quaternion; + } + var num5 = (float)Math.Sqrt(1f + m22 - m00 - m11); + var num2 = 0.5f / num5; + quaternion.X = (m20 + m02) * num2; + quaternion.Y = (m21 + m12) * num2; + quaternion.Z = 0.5f * num5; + quaternion.Y = (m01 - m10) * num2; + return quaternion; + } + + public static Vector3 VectorMultiply(this Quaternion rotation, Vector3 point) + { + var num1 = rotation.X * 2f; + var num2 = rotation.Y * 2f; + var num3 = rotation.Z * 2f; + var num4 = rotation.X * num1; + var num5 = rotation.Y * num2; + var num6 = rotation.Z * num3; + var num7 = rotation.X * num2; + var num8 = rotation.X * num3; + var num9 = rotation.Y * num3; + var num10 = rotation.W * num1; + var num11 = rotation.W * num2; + var num12 = rotation.W * num3; + Vector3 vector3; + vector3.X = (float) ((1.0 - (num5 + (double) num6)) * point.X + (num7 - (double) num12) * point.Y + (num8 + (double) num11) * point.Z); + vector3.Y = (float) ((num7 + (double) num12) * point.X + (1.0 - (num4 + (double) num6)) * point.Y + (num9 - (double) num10) * point.Z); + vector3.Z = (float) ((num8 - (double) num11) * point.X + (num9 + (double) num10) * point.Y + (1.0 - (num4 + (double) num5)) * point.Z); + return vector3; + } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/Transform.cs b/Uchu.World/Objects/Components/Transform.cs index b9ab70b8..778c634b 100644 --- a/Uchu.World/Objects/Components/Transform.cs +++ b/Uchu.World/Objects/Components/Transform.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Numerics; @@ -30,6 +31,28 @@ public void Rotate(Quaternion delta) GameObject.Serialize(GameObject); } + + public Vector3 Forward + { + get => Rotation.VectorMultiply(Vector3.UnitX); + set => Rotation = value.QuaternionLookRotation(Vector3.UnitY); + } + + public void LookAt(Vector3 position, bool lockY = true) + { + if (lockY) + { + position.Y = Position.Y; + } + + // Determine which direction to rotate towards + var targetDirection = position - Position; + + // Calculate a rotation a step closer to the target and applies rotation to this object + Rotation = targetDirection.QuaternionLookRotation(Vector3.UnitY); + + GameObject.Serialize(GameObject); + } public float Scale { get; set; } = -1; diff --git a/Uchu.World/Packets/GameMessages/Server/OrientToPositionMessage.cs b/Uchu.World/Packets/GameMessages/Server/OrientToPositionMessage.cs new file mode 100644 index 00000000..b5cf8d3c --- /dev/null +++ b/Uchu.World/Packets/GameMessages/Server/OrientToPositionMessage.cs @@ -0,0 +1,17 @@ +using System.Numerics; +using RakDotNet.IO; + +namespace Uchu.World +{ + public class OrientToPositionMessage : ServerGameMessage + { + public override GameMessageId GameMessageId => GameMessageId.OrientToPosition; + + public Vector3 Position { get; set; } + + public override void SerializeMessage(BitWriter writer) + { + writer.Write(Position); + } + } +} \ No newline at end of file From 691e57577880430274d7055974adc7dff525d42d Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Mon, 10 Feb 2020 12:17:09 +0100 Subject: [PATCH 15/23] Minor bug fixes; you can now view command return values without setting chat mode to debug. --- .../Extensions/TerrainFileExtensions.cs | 41 +++++++++++++++++++ Uchu.World/Handlers/SocialHandler.cs | 6 +-- Uchu.World/Objects/GameObjects/GameObject.cs | 8 ++++ Uchu.World/Objects/GameObjects/Player.cs | 19 +++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 Uchu.World/Extensions/TerrainFileExtensions.cs diff --git a/Uchu.World/Extensions/TerrainFileExtensions.cs b/Uchu.World/Extensions/TerrainFileExtensions.cs new file mode 100644 index 00000000..ed365705 --- /dev/null +++ b/Uchu.World/Extensions/TerrainFileExtensions.cs @@ -0,0 +1,41 @@ +using InfectedRose.Terrain; + +namespace Uchu.World +{ + public static class TerrainFileExtensions + { + public static float[,] CalculateHeightMap(this TerrainFile @this) + { + var heightMapWidth = @this.Chunks[0].HeightMap.Width; + var heightMapHeight = @this.Chunks[0].HeightMap.Height; + + var heights = new float[heightMapWidth * @this.Chunks.Count, heightMapHeight * @this.Chunks.Count]; + + // + // Render HeightMap + // + for (var chunkY = 0; chunkY < @this.ChunkCountY; ++chunkY) + { + for (var chunkX = 0; chunkX < @this.ChunkCountX; ++chunkX) + { + var chunk = @this.Chunks[chunkY * @this.ChunkCountX + chunkX]; + + for (var y = 0; y < chunk.HeightMap.Height; ++y) + { + for (var x = 0; x < chunk.HeightMap.Width; ++x) + { + var value = chunk.HeightMap.Data[y * chunk.HeightMap.Width + x]; + + var pixelX = chunkX * heightMapWidth + x; + var pixelY = chunkY * heightMapHeight + y; + + heights[pixelX, pixelY] = value; + } + } + } + } + + return heights; + } + } +} \ No newline at end of file diff --git a/Uchu.World/Handlers/SocialHandler.cs b/Uchu.World/Handlers/SocialHandler.cs index e3c46818..17a7db16 100644 --- a/Uchu.World/Handlers/SocialHandler.cs +++ b/Uchu.World/Handlers/SocialHandler.cs @@ -12,10 +12,6 @@ namespace Uchu.World.Handlers { public class SocialHandler : HandlerGroup { - // - // TODO: Move all of this to a component - // - [PacketHandler] public async Task ParseChatMessageHandler(ParseChatMessage message, Player player) { @@ -43,7 +39,7 @@ public async Task ParseChatMessageHandler(ParseChatMessage message, Player playe if (!string.IsNullOrWhiteSpace(response)) { - player.SendChatMessage(response); + player.SendChatMessage(response, PlayerChatChannel.Normal); } else { diff --git a/Uchu.World/Objects/GameObjects/GameObject.cs b/Uchu.World/Objects/GameObjects/GameObject.cs index 1b897e35..af109457 100644 --- a/Uchu.World/Objects/GameObjects/GameObject.cs +++ b/Uchu.World/Objects/GameObjects/GameObject.cs @@ -135,6 +135,14 @@ protected GameObject() Destruct(this); }); + + Listen(OnLayerChanged, mask => + { + foreach (var player in Zone.Players) + { + player.ViewUpdate(this); + } + }); } #region Operators diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index 6e33bf9f..32dddc7a 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -370,6 +370,25 @@ public void Teleport(Vector3 position) }); } + public void ViewUpdate(GameObject gameObject) + { + var spawned = Perspective.LoadedObjects.ToArray().Contains(gameObject); + + var view = Perspective.View(gameObject); + + if (spawned && !view) + { + Zone.SendDestruction(gameObject, this); + + return; + } + + if (!spawned && view) + { + Zone.SendConstruction(gameObject, this); + } + } + public void SendChatMessage(string message, PlayerChatChannel channel = PlayerChatChannel.Debug, Player author = null, ChatChannel chatChannel = World.ChatChannel.Public) { if (channel > ChatChannel) return; From c81c3fdad81d7a9a6602bed589a2b8d65df38562 Mon Sep 17 00:00:00 2001 From: wincent Date: Mon, 10 Feb 2020 22:40:10 +0100 Subject: [PATCH 16/23] 2D plane enemy movement trail --- Uchu.World/Behaviors/TacArcBehavior.cs | 14 ++- Uchu.World/Extensions/MathExtensions.cs | 16 ++- .../Commands/CharacterCommandHandler.cs | 46 +++++-- .../Commands/OperatorCommandHandler.cs | 74 +++++++++++ .../BaseCombatAIComponent.cs | 10 ++ .../ReplicaComponents/MovementAiComponent.cs | 118 ++++++++++++++++++ Uchu.World/Objects/Components/Transform.cs | 9 +- 7 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 Uchu.World/Objects/Components/ReplicaComponents/MovementAiComponent.cs diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index bffe5806..d5902099 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -123,7 +123,17 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution var distance = Vector3.Distance(transform.Position, sourcePosition); return distance <= context.MaxRange && context.MinRange <= distance; - }).ToArray(); + }).ToList(); + + /* + targets.ToList().Sort((g1, g2) => + { + var distance1 = Vector3.Distance(g1.Transform.Position, sourcePosition); + var distance2 = Vector3.Distance(g2.Transform.Position, sourcePosition); + + return (int) (distance1 - distance2); + }); + */ var selectedTargets = new List(); @@ -146,6 +156,8 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution if (any) { + baseCombatAiComponent.Target = selectedTargets.First(); + context.FoundTarget = true; if (CheckEnvironment) diff --git a/Uchu.World/Extensions/MathExtensions.cs b/Uchu.World/Extensions/MathExtensions.cs index c84e5f22..c3684f3e 100644 --- a/Uchu.World/Extensions/MathExtensions.cs +++ b/Uchu.World/Extensions/MathExtensions.cs @@ -69,7 +69,10 @@ public static Vector3 ToEuler(this Quaternion @this) return euler; } - public static Vector3 MoveTowards(this Vector3 current, Vector3 target, float maxDistanceDelta) + public static Vector3 MoveTowards(this Vector3 current, Vector3 target, float maxDistanceDelta) => + MoveTowards(current, target, maxDistanceDelta, out _); + + public static Vector3 MoveTowards(this Vector3 current, Vector3 target, float maxDistanceDelta, out Vector3 changeVector) { // // Stolen from Unity. @@ -82,10 +85,21 @@ public static Vector3 MoveTowards(this Vector3 current, Vector3 target, float ma var delta = (float) (deltaX * (double) deltaX + deltaY * (double) deltaY + deltaZ * (double) deltaZ); if (Math.Abs(delta) < 0.001f || delta <= maxDistanceDelta * (double) maxDistanceDelta) + { + changeVector = Vector3.Zero; + return target; + } var change = (float) Math.Sqrt(delta); + changeVector = new Vector3 + { + X = deltaX / change * maxDistanceDelta, + Y = deltaY / change * maxDistanceDelta, + Z = deltaZ / change * maxDistanceDelta + }; + return new Vector3( current.X + deltaX / change * maxDistanceDelta, current.Y + deltaY / change * maxDistanceDelta, diff --git a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs index 465c8e27..3f785a4e 100644 --- a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs @@ -80,7 +80,7 @@ public string ChangeCoin(string[] arguments, Player player) [CommandHandler(Signature = "spawn", Help = "Spawn an object", GameMasterLevel = GameMasterLevel.Admin)] public string Spawn(string[] arguments, Player player) { - if (arguments.Length != 1 || arguments.Length > 4) + if (arguments.Length == default) return "spawn "; arguments = arguments.Select(a => a.Replace('.', ',')).ToArray(); @@ -88,20 +88,28 @@ public string Spawn(string[] arguments, Player player) if (!int.TryParse(arguments[0], out var lot)) return "Invalid "; var position = player.Transform.Position; - if (arguments.Length >= 4) - try + + var factionSided = arguments.Contains("-f"); + + if (!factionSided) + { + if (arguments.Length >= 4) { - position = new Vector3 + try { - X = float.Parse(arguments[1].Replace('.', ',')), - Y = float.Parse(arguments[2].Replace('.', ',')), - Z = float.Parse(arguments[3].Replace('.', ',')) - }; - } - catch - { - return "Invalid , , or "; + position = new Vector3 + { + X = float.Parse(arguments[1].Replace('.', ',')), + Y = float.Parse(arguments[2].Replace('.', ',')), + Z = float.Parse(arguments[3].Replace('.', ',')) + }; + } + catch + { + return "Invalid , , or "; + } } + } var rotation = player.Transform.Rotation; @@ -117,6 +125,20 @@ public string Spawn(string[] arguments, Player player) Object.Start(obj); GameObject.Construct(obj); + if (factionSided) + { + if (obj.TryGetComponent(out var stats)) + { + stats.Factions = new[] {int.Parse(arguments[2])}; + stats.Enemies = new[] {int.Parse(arguments[3])}; + + obj.GetComponent().ResurrectTime = 30; + obj.GetComponent().Enabled = false; + + GameObject.Serialize(obj); + } + } + return $"Successfully spawned {lot} at\npos: {position}\nrot: {rotation}"; } diff --git a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs index c9316f41..f2ded671 100644 --- a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.Scripting.Utils; using Uchu.Core; using Uchu.World.Scripting.Managed; using Uchu.World.Social; @@ -199,5 +200,78 @@ public string Ping(string[] arguments, Player player) { return $"{player.Ping}ms"; } + + [CommandHandler(Signature = "movement", Help = "Toggle the movement of an npc", GameMasterLevel = GameMasterLevel.Admin)] + public string Movement(string[] arguments, Player player) + { + var targets = new List(); + + Ai(arguments, player); + + if (arguments.Contains("all")) + { + targets = player.Zone.Objects.OfType().Select( + m => m.GameObject + ).ToList(); + } + else + { + var current = player.Zone.GameObjects.First(); + foreach (var gameObject in player.Zone.GameObjects.Where(g => g != player && g != default)) + { + if (gameObject.Transform == default) continue; + + if (gameObject.GetComponent() != default) continue; + + if (Vector3.Distance(current.Transform.Position, player.Transform.Position) > + Vector3.Distance(gameObject.Transform.Position, player.Transform.Position)) + current = gameObject; + } + + targets.Add(current); + } + + foreach (var current in targets) + { + if (!current.TryGetComponent(out var movementAiComponent)) + return $"{current} does not have a movement AI component"; + + movementAiComponent.Enabled = !movementAiComponent.Enabled; + } + + return "Toggled movement for agents"; + } + + [CommandHandler(Signature = "ai", Help = "Toggle the ai all npcs", GameMasterLevel = GameMasterLevel.Admin)] + public static string Ai(string[] arguments, Player player) + { + foreach (var component in player.Zone.Objects.OfType()) + { + component.Enabled = true; + } + + return "Toggled"; + } + + + [CommandHandler(Signature = "target", Help = "Get target of npc", GameMasterLevel = GameMasterLevel.Admin)] + public string Target(string[] arguments, Player player) + { + var current = player.Zone.GameObjects.First(); + foreach (var gameObject in player.Zone.GameObjects.Where(g => g != player && g != default)) + { + if (gameObject.Transform == default) continue; + + if (gameObject.GetComponent() != default) continue; + + if (Vector3.Distance(current.Transform.Position, player.Transform.Position) > + Vector3.Distance(gameObject.Transform.Position, player.Transform.Position)) + current = gameObject; + } + + if (!current.TryGetComponent(out var baseCombatAiComponent)) return "Invalid nearby"; + + return $"Target: {baseCombatAiComponent.Target}"; + } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs index 918853ef..f5b6fa0a 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/BaseCombatAIComponent.cs @@ -21,6 +21,8 @@ public class BaseCombatAiComponent : ReplicaComponent public float Cooldown { get; set; } + public bool AbilityDowntime { get; set; } + public List SkillEntries { get; set; } private SkillComponent SkillComponent { get; set; } @@ -30,6 +32,8 @@ public class BaseCombatAiComponent : ReplicaComponent private QuickBuildComponent QuickBuildComponent { get; set; } private Stats Stats { get; set; } + + public bool Enabled { get; set; } = true; public BaseCombatAiComponent() { @@ -57,6 +61,8 @@ public BaseCombatAiComponent() Listen(OnTick, async () => { + if (!Enabled) return; + if (!DestructibleComponent.Alive) return; if (QuickBuildComponent != default && QuickBuildComponent.State != RebuildState.Completed) return; @@ -65,6 +71,8 @@ public BaseCombatAiComponent() { await using var ctx = new CdClientContext(); + AbilityDowntime = false; + Cooldown = 1f; foreach (var entry in SkillEntries.Where(s => !s.Cooldown)) @@ -73,6 +81,8 @@ public BaseCombatAiComponent() if (time.Equals(0)) continue; + AbilityDowntime = true; + entry.Cooldown = true; var skillInfo = await ctx.SkillBehaviorTable.FirstAsync( diff --git a/Uchu.World/Objects/Components/ReplicaComponents/MovementAiComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/MovementAiComponent.cs new file mode 100644 index 00000000..379834ba --- /dev/null +++ b/Uchu.World/Objects/Components/ReplicaComponents/MovementAiComponent.cs @@ -0,0 +1,118 @@ +using System.Linq; +using System.Numerics; +using Microsoft.EntityFrameworkCore; +using RakDotNet.IO; +using Uchu.Core.Client; + +namespace Uchu.World +{ + public class MovementAiComponent : ReplicaComponent + { + public override ComponentId Id => ComponentId.MovementAIComponent; + + private MovementAIComponent ClientInfo { get; set; } + + private BaseCombatAiComponent BaseCombatAiComponent { get; set; } + + private ControllablePhysicsComponent ControllablePhysicsComponent { get; set; } + + private float Speed { get; set; } + + public bool Enabled { get; set; } + + private Vector3 Origin { get; set; } + + protected MovementAiComponent() + { + Listen(OnStart, async () => + { + await using var ctx = new CdClientContext(); + + var info = await ctx.MovementAIComponentTable.FirstOrDefaultAsync( + m => m.Id == GameObject.Lot.GetComponentId(ComponentId.MovementAIComponent) + ); + + if (info == default) + { + Destroy(this); + + return; + } + + BaseCombatAiComponent = GameObject.GetComponent(); + + ControllablePhysicsComponent = GameObject.GetComponent(); + + ClientInfo = info; + + Speed = ClientInfo.WanderSpeed ?? 0; + + Speed *= 3; + + Origin = Transform.Position; + }); + + Listen(OnTick, () => + { + if (!Enabled) return; + + if (BaseCombatAiComponent == default) + { + BaseCombatAiComponent = GameObject.GetComponent(); + } + + if (ControllablePhysicsComponent == default) + { + ControllablePhysicsComponent = GameObject.GetComponent(); + } + + if (BaseCombatAiComponent.Cooldown > 0 && BaseCombatAiComponent.AbilityDowntime) return; + + var targets = BaseCombatAiComponent.SeekValidTargets().Where(target => + { + var transform = target.Transform; + + var distance = Vector3.Distance(transform.Position, Transform.Position); + + return distance <= 50; + }).ToList(); + + targets.ToList().Sort((g1, g2) => + { + var distance1 = Vector3.Distance(g1.Transform.Position, Transform.Position); + var distance2 = Vector3.Distance(g2.Transform.Position, Transform.Position); + + return (int) (distance2 - distance1); + }); + + var position = Transform.Position; + + var targetPosition = BaseCombatAiComponent?.Target?.Transform?.Position ?? + targets.FirstOrDefault()?.Transform?.Position ?? + Origin; + + targetPosition.Y = position.Y; + + position = position.MoveTowards(targetPosition, Speed, out var delta); + + Transform.Position = position; + + var prev = ControllablePhysicsComponent.Velocity; + + ControllablePhysicsComponent.Velocity = delta; + + ControllablePhysicsComponent.HasVelocity = delta != prev; + + Transform.LookAt(targetPosition); + }); + } + + public override void Construct(BitWriter writer) + { + } + + public override void Serialize(BitWriter writer) + { + } + } +} \ No newline at end of file diff --git a/Uchu.World/Objects/Components/Transform.cs b/Uchu.World/Objects/Components/Transform.cs index 778c634b..b647d00a 100644 --- a/Uchu.World/Objects/Components/Transform.cs +++ b/Uchu.World/Objects/Components/Transform.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Numerics; @@ -32,12 +31,20 @@ public void Rotate(Quaternion delta) GameObject.Serialize(GameObject); } + /// + /// The forward direction for this object + /// public Vector3 Forward { get => Rotation.VectorMultiply(Vector3.UnitX); set => Rotation = value.QuaternionLookRotation(Vector3.UnitY); } + /// + /// Make this object look at a given position + /// + /// Position to look at + /// Ignore the Y axis (Default = true) public void LookAt(Vector3 position, bool lockY = true) { if (lockY) From 15aefd91100e33fe0b39d650ce9bf45305643dd4 Mon Sep 17 00:00:00 2001 From: wincent Date: Tue, 11 Feb 2020 11:08:07 +0100 Subject: [PATCH 17/23] Begun working on handling explicit client skill targeting. --- Uchu.World/Behaviors/BehaviorTree.cs | 10 +++-- Uchu.World/Behaviors/ExecutionContext.cs | 2 + Uchu.World/Behaviors/TacArcBehavior.cs | 26 +++++++++---- .../Commands/OperatorCommandHandler.cs | 1 - .../Handlers/GameMessages/SkillHandler.cs | 38 ++++++++++++------- .../ReplicaComponents/SkillComponent.cs | 30 +++++++++++++-- 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 316ac651..35e47104 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -192,12 +192,14 @@ public async Task CalculateAsync(GameObject associate, BitW /// Client skill data /// Data to be sent to clients /// Type of skill - /// - /// + /// Explicit target /// Context - public async Task ExecuteAsync(GameObject associate, BitReader reader, BitWriter writer, SkillCastType castType = SkillCastType.OnEquip, GameObject target = default, bool explicitTarget = false) + public async Task ExecuteAsync(GameObject associate, BitReader reader, BitWriter writer, SkillCastType castType = SkillCastType.OnEquip, GameObject target = default) { - var context = new ExecutionContext(associate, reader, writer); + var context = new ExecutionContext(associate, reader, writer) + { + ExplicitTarget = target + }; if (RootBehaviors.TryGetValue(SkillCastType.Default, out var defaultList)) { diff --git a/Uchu.World/Behaviors/ExecutionContext.cs b/Uchu.World/Behaviors/ExecutionContext.cs index 5022f5cf..3c5dccc2 100644 --- a/Uchu.World/Behaviors/ExecutionContext.cs +++ b/Uchu.World/Behaviors/ExecutionContext.cs @@ -17,6 +17,8 @@ public class ExecutionContext public BitWriter Writer { get; set; } + public GameObject ExplicitTarget { get; set; } + public Dictionary BehaviorHandles { get; } = new Dictionary(); public ExecutionContext(GameObject associate, BitReader reader, BitWriter writer) diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index d5902099..de342028 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -38,11 +38,27 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc { await base.ExecuteAsync(context, branchContext); + if (context.ExplicitTarget != null) + { + var branch = new ExecutionBranchContext(context.ExplicitTarget) + { + Duration = branchContext.Duration + }; + + await ActionBehavior.ExecuteAsync(context, branch); + + context.ExplicitTarget = null; + + return; + } + var hit = context.Reader.ReadBit(); context.Writer.Write(hit); if (hit) // Hit { + var targets = new List(); + if (CheckEnvironment) { var checkEnvironment = context.Reader.ReadBit(); // Check environment @@ -53,22 +69,20 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc var specifiedTargets = context.Reader.Read(); context.Writer.Write(specifiedTargets); - - var targets = new List(); for (var i = 0; i < specifiedTargets; i++) { var targetId = context.Reader.Read(); context.Writer.Write(targetId); - + if (!context.Associate.Zone.TryGetGameObject(targetId, out var target)) { Logger.Error($"{context.Associate} sent invalid TacArc target: {targetId}"); - + continue; } - + targets.Add(target); } @@ -125,7 +139,6 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution return distance <= context.MaxRange && context.MinRange <= distance; }).ToList(); - /* targets.ToList().Sort((g1, g2) => { var distance1 = Vector3.Distance(g1.Transform.Position, sourcePosition); @@ -133,7 +146,6 @@ public override async Task CalculateAsync(NpcExecutionContext context, Execution return (int) (distance1 - distance2); }); - */ var selectedTargets = new List(); diff --git a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs index f2ded671..2fd6850b 100644 --- a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs @@ -252,7 +252,6 @@ public static string Ai(string[] arguments, Player player) return "Toggled"; } - [CommandHandler(Signature = "target", Help = "Get target of npc", GameMasterLevel = GameMasterLevel.Admin)] public string Target(string[] arguments, Player player) diff --git a/Uchu.World/Handlers/GameMessages/SkillHandler.cs b/Uchu.World/Handlers/GameMessages/SkillHandler.cs index 5704de2a..b3b2ddb4 100644 --- a/Uchu.World/Handlers/GameMessages/SkillHandler.cs +++ b/Uchu.World/Handlers/GameMessages/SkillHandler.cs @@ -10,27 +10,37 @@ public class SkillHandler : HandlerGroup [PacketHandler] public void SkillStartHandler(StartSkillMessage message, Player player) { - try + Task.Run(() => { - Task.Run(() => player.GetComponent().StartUserSkillAsync(message)); - } - catch (Exception e) - { - Logger.Error($"Skill Execution failed: {e.Message}\n{e.StackTrace}"); - } + try + { + return player.GetComponent().StartUserSkillAsync(message); + } + catch (Exception e) + { + Logger.Error(e); + + return Task.CompletedTask; + } + }); } [PacketHandler] public void SyncSkillHandler(SyncSkillMessage message, Player player) { - try - { - Task.Run(() => player.GetComponent().SyncUserSkillAsync(message)); - } - catch (Exception e) + Task.Run(() => { - Logger.Error($"Skill Syncing failed: {e.Message}\n{e.StackTrace}"); - } + try + { + return player.GetComponent().SyncUserSkillAsync(message); + } + catch (Exception e) + { + Logger.Error(e); + + return Task.CompletedTask; + } + }); } [PacketHandler] diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index c6d8257a..19d1bf69 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using RakDotNet.IO; @@ -184,7 +185,8 @@ public async Task CalculateSkillAsync(int skillId) Content = stream.ToArray(), SkillId = skillId, SkillHandle = syncId, - OptionalOriginator = GameObject + OptionalOriginator = GameObject, + OriginatorRotation = GameObject.Transform.Rotation }); return context.SkillTime; @@ -194,10 +196,30 @@ public async Task StartUserSkillAsync(StartSkillMessage message) { if (As() == null) return; + As().SendChatMessage($"TARGET: {message.OptionalTarget} [{message.UsedMouse}]"); + + try + { + if (message.OptionalTarget != null) + { + // There should be more to this + if (!message.OptionalTarget.GetComponent().Alive) + message.OptionalTarget = null; + else if (Vector3.Distance(message.OptionalTarget.Transform.Position, Transform.Position) > TargetRange) + message.OptionalTarget = null; + } + } + catch (Exception e) + { + Logger.Error(e); + + return; + } + var stream = new MemoryStream(message.Content); using (var reader = new BitReader(stream, leaveOpen: true)) { - As().SendChatMessage($"START: {message.SkillId}"); + As().SendChatMessage($"START: {message.SkillId} [{message.Content.Length}]"); var tree = new BehaviorTree(message.SkillId); @@ -234,7 +256,7 @@ public async Task StartUserSkillAsync(StartSkillMessage message) Associate = GameObject, CasterLatency = message.CasterLatency, CastType = message.CastType, - Content = writeStream.ToArray(), + Content = message.Content, LastClickedPosition = message.LastClickedPosition, OptionalOriginator = message.OptionalOriginator, OptionalTarget = message.OptionalTarget, @@ -276,7 +298,7 @@ public async Task SyncUserSkillAsync(SyncSkillMessage message) { Associate = GameObject, BehaviorHandle = message.BehaviorHandle, - Content = writeStream.ToArray(), + Content = message.Content, Done = message.Done, SkillHandle = message.SkillHandle }, As()); From 1225d25741326e9108c2e2fb07f2046b9f5b8ec0 Mon Sep 17 00:00:00 2001 From: wincent Date: Tue, 11 Feb 2020 11:33:48 +0100 Subject: [PATCH 18/23] Using "use_picked_target" to check if explicit target should be used in TacArc --- Uchu.World/Behaviors/TacArcBehavior.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Uchu.World/Behaviors/TacArcBehavior.cs b/Uchu.World/Behaviors/TacArcBehavior.cs index de342028..e759061e 100644 --- a/Uchu.World/Behaviors/TacArcBehavior.cs +++ b/Uchu.World/Behaviors/TacArcBehavior.cs @@ -22,6 +22,8 @@ public class TacArcBehavior : BehaviorBase public int MaxTargets { get; set; } + public bool UsePickedTarget { get; set; } + public override async Task BuildAsync() { CheckEnvironment = (await GetParameter("check_env"))?.Value > 0; @@ -32,13 +34,15 @@ public override async Task BuildAsync() MissBehavior = await GetBehavior("miss action"); MaxTargets = await GetParameter("max targets"); + + UsePickedTarget = await GetParameter("use_picked_target") > 0; } public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) { await base.ExecuteAsync(context, branchContext); - if (context.ExplicitTarget != null) + if (UsePickedTarget && context.ExplicitTarget != null) { var branch = new ExecutionBranchContext(context.ExplicitTarget) { @@ -47,8 +51,6 @@ public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranc await ActionBehavior.ExecuteAsync(context, branch); - context.ExplicitTarget = null; - return; } From 8d9cbaf316a80cbda4a090ed0262a4aa3467a0d0 Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 15 Feb 2020 15:45:52 +0100 Subject: [PATCH 19/23] Proxy items now work properly. Started working on triggers. --- InfectedRose | 2 +- Uchu.Core/Enums/ZoneId.cs | 3 +- Uchu.Core/IO/IFileResources.cs | 2 + Uchu.Core/IO/LocalResources.cs | 8 +- Uchu.World/Behaviors/BehaviorTree.cs | 4 +- .../Behaviors/DamageAbsorptionBehavior.cs | 14 + Uchu.World/Client/ZoneInfo.cs | 2 +- Uchu.World/Client/ZoneParser.cs | 52 +--- Uchu.World/Enums/PhantomPhysicsEffectType.cs | 11 + .../Commands/CharacterCommandHandler.cs | 10 +- .../Commands/OperatorCommandHandler.cs | 1 + .../Handlers/WorldInitializationHandler.cs | 1 + .../Components/Player/Concepts/Inventory.cs | 1 - .../ReplicaComponents/InventoryComponent.cs | 246 +++++++++--------- .../PhantomPhysicsComponent.cs | 4 +- .../ReplicaComponents/SkillComponent.cs | 70 ++--- .../ReplicaComponents/TriggerComponent.cs | 77 +++++- Uchu.World/Objects/GameObjects/GameObject.cs | 2 +- .../Objects/GameObjects/InstancingUtil.cs | 5 + Uchu.World/Objects/GameObjects/Player.cs | 34 +-- Uchu.World/Uchu.World.csproj | 1 + 21 files changed, 302 insertions(+), 248 deletions(-) create mode 100644 Uchu.World/Behaviors/DamageAbsorptionBehavior.cs create mode 100644 Uchu.World/Enums/PhantomPhysicsEffectType.cs diff --git a/InfectedRose b/InfectedRose index 863112bc..0a93e1e1 160000 --- a/InfectedRose +++ b/InfectedRose @@ -1 +1 @@ -Subproject commit 863112bcd87d4f71904884aba1b15418e0bc2346 +Subproject commit 0a93e1e1d6b86e8c3a5f31e43441f9752fb61078 diff --git a/Uchu.Core/Enums/ZoneId.cs b/Uchu.Core/Enums/ZoneId.cs index 1bfbcdb6..9da5481c 100644 --- a/Uchu.Core/Enums/ZoneId.cs +++ b/Uchu.Core/Enums/ZoneId.cs @@ -35,6 +35,7 @@ public enum ZoneId : ushort NexusTower = 1900, Ninjago = 2000, FrakjawBattle, - NimbusStationWinterRacetrack = 1261 + NimbusStationWinterRacetrack = 1261, + Test = 58001 } } \ No newline at end of file diff --git a/Uchu.Core/IO/IFileResources.cs b/Uchu.Core/IO/IFileResources.cs index 88001ffb..cd122121 100644 --- a/Uchu.Core/IO/IFileResources.cs +++ b/Uchu.Core/IO/IFileResources.cs @@ -6,6 +6,8 @@ namespace Uchu.Core.IO { public interface IFileResources { + string RootPath { get; } + Task ReadTextAsync(string path); Task ReadBytesAsync(string path); diff --git a/Uchu.Core/IO/LocalResources.cs b/Uchu.Core/IO/LocalResources.cs index 4353c7a4..d3e91771 100644 --- a/Uchu.Core/IO/LocalResources.cs +++ b/Uchu.Core/IO/LocalResources.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -11,6 +10,8 @@ public class LocalResources : IFileResources { private readonly Configuration _config; + public string RootPath => _config.ResourcesConfiguration.GameResourceFolder; + public LocalResources(Configuration config) { _config = config; @@ -20,7 +21,7 @@ public async Task ReadTextAsync(string path) { await using var stream = GetStream(path); using var reader = new StreamReader(stream); - return await reader.ReadToEndAsync(); + return await reader.ReadToEndAsync().ConfigureAwait(false); } public async Task ReadBytesAsync(string path) @@ -77,8 +78,7 @@ public IEnumerable GetAllFilesWithExtension(string location, string exte return files; } - - + public Stream GetStream(string path) { path = path.Replace('\\', '/').ToLower(); diff --git a/Uchu.World/Behaviors/BehaviorTree.cs b/Uchu.World/Behaviors/BehaviorTree.cs index 35e47104..8cd4509f 100644 --- a/Uchu.World/Behaviors/BehaviorTree.cs +++ b/Uchu.World/Behaviors/BehaviorTree.cs @@ -281,9 +281,9 @@ public async Task DismantleAsync(GameObject associate) return context; } - public static async Task GetSkillsForItem(Item item) + public static async Task GetSkillsForObject(Lot lot) { - var tree = new BehaviorTree(item.Lot); + var tree = new BehaviorTree(lot); return await tree.BuildAsync(); } diff --git a/Uchu.World/Behaviors/DamageAbsorptionBehavior.cs b/Uchu.World/Behaviors/DamageAbsorptionBehavior.cs new file mode 100644 index 00000000..53c5b7a5 --- /dev/null +++ b/Uchu.World/Behaviors/DamageAbsorptionBehavior.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace Uchu.World.Behaviors +{ + public class DamageAbsorptionBehavior : BehaviorBase + { + public override BehaviorTemplateId Id => BehaviorTemplateId.DamageAbsorption; + + public override Task BuildAsync() + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Uchu.World/Client/ZoneInfo.cs b/Uchu.World/Client/ZoneInfo.cs index 33817f23..91e30926 100644 --- a/Uchu.World/Client/ZoneInfo.cs +++ b/Uchu.World/Client/ZoneInfo.cs @@ -12,7 +12,7 @@ public class ZoneInfo public TerrainFile TerrainFile { get; set; } - public List Triggers { get; set; } + public TriggerDictionary TriggerDictionary { get; set; } public List LvlFiles { get; set; } } diff --git a/Uchu.World/Client/ZoneParser.cs b/Uchu.World/Client/ZoneParser.cs index 81f1526b..61ce7a50 100644 --- a/Uchu.World/Client/ZoneParser.cs +++ b/Uchu.World/Client/ZoneParser.cs @@ -52,8 +52,6 @@ public async Task LoadZoneDataAsync(int seek) var path = Path.GetDirectoryName(luzFile); - var triggers = await GetTriggers(path); - var lvlFiles = new List(); foreach (var scene in luz.Scenes) @@ -62,6 +60,8 @@ public async Task LoadZoneDataAsync(int seek) using var sceneReader = new BitReader(sceneStream); + Logger.Information($"Parsing: {scene.FileName}"); + var lvl = new LvlFile(); lvl.Deserialize(sceneReader); @@ -84,15 +84,19 @@ public async Task LoadZoneDataAsync(int seek) terrain.Deserialize(terrainReader); - Logger.Information($"Parsed: {(ZoneId) luz.WorldId}"); + var triggers = await TriggerDictionary.FromDirectoryAsync(Path.Combine(_resources.RootPath, path)); + Logger.Information($"Parsed: {(ZoneId) luz.WorldId}"); + Zones[(ZoneId) luz.WorldId] = new ZoneInfo { LuzFile = luz, LvlFiles = lvlFiles, - Triggers = triggers.ToList(), + TriggerDictionary = triggers, TerrainFile = terrain }; + + break; } catch (Exception e) { @@ -100,45 +104,5 @@ public async Task LoadZoneDataAsync(int seek) } } } - - private async Task GetTriggers(string path) - { - var files = _resources.GetAllFilesWithExtension(path, "lutriggers"); - - var triggerCollection = new List(); - - foreach (var file in files) - { - await using var stream = File.OpenRead(file); - - var triggers = (TriggerCollection) _triggerSerializer.Deserialize(stream); - - var fileName = Path.GetFileNameWithoutExtension(file); - - var parts = fileName.Split('_'); - - foreach (var part in parts) - { - // - // I don't know if there is a better way of getting this ID. - // - - if (!int.TryParse(part, out var primaryId)) continue; - - /* - foreach (var trigger in triggers.Triggers) - { - trigger.Id = primaryId; - } - */ - - triggerCollection.AddRange(triggers.Triggers); - - break; - } - } - - return triggerCollection.ToArray(); - } } } \ No newline at end of file diff --git a/Uchu.World/Enums/PhantomPhysicsEffectType.cs b/Uchu.World/Enums/PhantomPhysicsEffectType.cs new file mode 100644 index 00000000..5f6fe924 --- /dev/null +++ b/Uchu.World/Enums/PhantomPhysicsEffectType.cs @@ -0,0 +1,11 @@ +namespace Uchu.World +{ + public enum PhantomPhysicsEffectType + { + Push = 0, + Attract = 1, + Repulse = 2, + Gravity = 3, + Friction = 4, + } +} \ No newline at end of file diff --git a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs index 3f785a4e..5079e100 100644 --- a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs @@ -214,7 +214,15 @@ public string Near(string[] arguments, Player player) { if (gameObject.Transform == default) continue; - if (gameObject.GetComponent() != default) continue; + if (!arguments.Contains("-sp")) + { + if (gameObject.GetComponent() != default) continue; + } + + if (arguments.Contains("-t")) + { + if (!gameObject.TryGetComponent(out _)) continue; + } if (Vector3.Distance(current.Transform.Position, player.Transform.Position) > Vector3.Distance(gameObject.Transform.Position, player.Transform.Position)) diff --git a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs index 2fd6850b..ac2c6bba 100644 --- a/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/OperatorCommandHandler.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Scripting.Utils; using Uchu.Core; +using Uchu.World.AI; using Uchu.World.Scripting.Managed; using Uchu.World.Social; diff --git a/Uchu.World/Handlers/WorldInitializationHandler.cs b/Uchu.World/Handlers/WorldInitializationHandler.cs index 93342dea..770720df 100644 --- a/Uchu.World/Handlers/WorldInitializationHandler.cs +++ b/Uchu.World/Handlers/WorldInitializationHandler.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; diff --git a/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs b/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs index abb025fd..f08e5f71 100644 --- a/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs +++ b/Uchu.World/Objects/Components/Player/Concepts/Inventory.cs @@ -61,7 +61,6 @@ public int Size internal Inventory(InventoryType inventoryType, InventoryManagerComponent managerComponent) { - InventoryType = inventoryType; ManagerComponent = managerComponent; diff --git a/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs index 026aa424..ea1bf8ce 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs @@ -22,7 +22,7 @@ public class InventoryComponent : ReplicaComponent private Dictionary ProxyItems { get; set; } = new Dictionary(); public override ComponentId Id => ComponentId.InventoryComponent; - + protected InventoryComponent() { Listen(OnDestroyed, () => @@ -31,10 +31,10 @@ protected InventoryComponent() OnUnEquipped.Clear(); }); - Listen(OnStart, () => + Listen(OnStart, async () => { - using var cdClient = new CdClientContext(); - + await using var cdClient = new CdClientContext(); + var component = cdClient.ComponentsRegistryTable.FirstOrDefault(c => c.Id == GameObject.Lot && c.Componenttype == (int) ComponentId.InventoryComponent); @@ -64,53 +64,12 @@ protected InventoryComponent() Debug.Assert(item.Itemid != null, "item.Itemid != null"); Debug.Assert(item.Count != null, "item.Count != null"); - - Items.TryAdd(itemComponent.EquipLocation, new InventoryItem - { - InventoryItemId = IdUtilities.GenerateObjectId(), - Count = (long) item.Count, - LOT = (int) item.Itemid, - Slot = -1, - InventoryType = -1 - }); + + await MountItemAsync(item.Itemid ?? 0, IdUtilities.GenerateObjectId()); } }); } - public long EquipUnmanagedItem(Lot lot, uint count = 1, int slot = -1, - InventoryType inventoryType = InventoryType.None) - { - using var cdClient = new CdClientContext(); - var cdClientObject = cdClient.ObjectsTable.FirstOrDefault( - o => o.Id == lot - ); - - var itemRegistryEntry = lot.GetComponentId(ComponentId.ItemComponent); - - if (cdClientObject == default || itemRegistryEntry == default) - { - Logger.Error($"{lot} is not a valid item"); - return -1; - } - - var itemComponent = cdClient.ItemComponentTable.First( - i => i.Id == itemRegistryEntry - ); - - var id = IdUtilities.GenerateObjectId(); - - Items[itemComponent.EquipLocation] = new InventoryItem - { - InventoryItemId = id, - Count = count, - Slot = slot, - LOT = lot, - InventoryType = (int) inventoryType - }; - - return id; - } - public async Task EquipItemAsync(Item item, bool ignoreAllChecks = false) { if (item?.InventoryItem == null) @@ -132,45 +91,14 @@ public async Task EquipItemAsync(Item item, bool ignoreAllChecks = false) } } } - - /* - * Equip proxies - */ - - var proxies = await GenerateProxyItemsAsync(item); - - if (proxies?.Length > 0) - { - ProxyItems[item.ObjectId] = proxies; - } Logger.Debug($"Equipping {item}"); item.Equipped = true; - var items = Items.Select(i => (i.Key, i.Value)).ToArray(); - - foreach (var (equipLocation, value) in items) - { - if (!equipLocation.Equals(item.ItemComponent.EquipLocation)) continue; - - var manager = GameObject.GetComponent(); - - if (manager != default) - { - var oldItem = manager.FindItem(value.InventoryItemId); - - await UnEquipItemAsync(oldItem); - } - else - { - await UnEquipItemAsync(value.InventoryItemId); - } - } - await OnEquipped.InvokeAsync(item); - Items[item.ItemComponent.EquipLocation] = item.InventoryItem; + await MountItemAsync(item.Lot, item.ObjectId, item.Settings); await ChangeEquippedSateOnPlayerAsync(item.ObjectId, true); @@ -185,43 +113,14 @@ public async Task UnEquipItemAsync(Item item) if (item != null) { - item.Equipped = false; - - await UnEquipItemAsync(item.ObjectId); - } - } - - private async Task UnEquipItemAsync(long id) - { - var (equipLocation, value) = Items.FirstOrDefault(i => i.Value.InventoryItemId == id); - - if (value == default) - { - // - // It's quite common for the client to send un-equip requests for items that it uses or whatever. - // - return; - } - - Items.Remove(equipLocation); - - await ChangeEquippedSateOnPlayerAsync(id, false); - - GameObject.Serialize(GameObject); - - if (ProxyItems.TryGetValue(id, out var proxies)) - { - foreach (var proxy in proxies) - { - await UnEquipItemAsync(proxy); - } + await MountItemAsync(item.Lot, 0); } } private async Task ChangeEquippedSateOnPlayerAsync(long itemId, bool equipped) { if (!(GameObject is Player)) return; - + await using var ctx = new UchuContext(); var inventoryItem = await ctx.InventoryItems.FirstOrDefaultAsync(i => i.InventoryItemId == itemId); @@ -234,34 +133,125 @@ private async Task ChangeEquippedSateOnPlayerAsync(long itemId, bool equipped) await ctx.SaveChangesAsync(); } - private async Task GenerateProxyItemsAsync(Item item) + private async Task GenerateProxyItemsAsync(Lot item) { - if (string.IsNullOrWhiteSpace(item?.ItemComponent?.SubItems)) return null; + await using var ctx = new CdClientContext(); + + var itemInfo = await ctx.ItemComponentTable.FirstOrDefaultAsync( + i => i.Id == item.GetComponentId(ComponentId.ItemComponent) + ); + + if (itemInfo == default) return new Lot[0]; + + if (string.IsNullOrWhiteSpace(itemInfo.SubItems)) return new Lot[0]; - var proxies = item.ItemComponent.SubItems + var proxies = itemInfo.SubItems .Replace(" ", "") .Split(',') - .Select(int.Parse); + .Select(i => (Lot) int.Parse(i)); + + return proxies.ToArray(); + } + + public async Task MountItemAsync(Lot inventoryItem, long id, LegoDataDictionary settings = default) + { + await using var ctx = new CdClientContext(); + + var itemInfo = await ctx.ItemComponentTable.FirstOrDefaultAsync( + i => i.Id == inventoryItem.GetComponentId(ComponentId.ItemComponent) + ); + + if (itemInfo == default) return -1; + + var location = (EquipLocation) itemInfo.EquipLocation; + + var skills = GameObject.TryGetComponent(out var skillComponent); + + if (Items.TryGetValue(location, out var oldItem) && oldItem != default && skills) + { + foreach (var (key, proxyItems) in ProxyItems) + { + if (!proxyItems.Contains(oldItem.InventoryItemId)) continue; + + var item = Items.Values.Where(v => v != default).FirstOrDefault( + v => v.InventoryItemId == key + ); + + if (item == default) goto equipItem; + + await MountItemAsync(item.LOT, 0); + + goto equipItem; + } + + await skillComponent.DismountItemAsync(oldItem.LOT); + + if (ProxyItems.TryGetValue(oldItem.InventoryItemId, out var values)) + { + ProxyItems.Remove(oldItem.InventoryItemId); + + foreach (var value in values) + { + var item = Items.Values.Where(v => v != default).FirstOrDefault( + v => v.InventoryItemId == value + ); + + if (item == default) continue; - var list = new List(); + await MountItemAsync(item.LOT, 0); + } + } + } + + equipItem: - await using var cdClient = new CdClientContext(); + if (id == default) + { + Items[location] = default; + + GameObject.Serialize(GameObject); - foreach (Lot proxy in proxies) + return -1; + } + + var inventoryType = ((ItemType) (itemInfo.ItemType ?? 0)).GetInventoryType(); + + Items[location] = new InventoryItem { - var componentId = proxy.GetComponentId(ComponentId.ItemComponent); + LOT = inventoryItem, + Count = 1, + ExtraInfo = settings?.ToString(), + InventoryItemId = id, + InventoryType = (int) inventoryType, + Slot = -1 + }; - var component = await cdClient.ItemComponentTable.FirstOrDefaultAsync(i => i.Id == componentId); + GameObject.Serialize(GameObject); + + if (!skills || inventoryItem == default) return -1; + + await skillComponent.MountItemAsync(inventoryItem); + + /* + * Equip proxies + */ - if (component == default) continue; + var additionalItems = await GenerateProxyItemsAsync(inventoryItem); - list.Add(EquipUnmanagedItem( - proxy, - inventoryType: item.Inventory.InventoryType) - ); + if (additionalItems.Length <= 0) return id; + + var proxies = new List(); + + foreach (var proxy in additionalItems) + { + var proxyId = await MountItemAsync(proxy, IdUtilities.GenerateObjectId()); + + proxies.Add(proxyId); } - return list.ToArray(); + ProxyItems[id] = proxies.ToArray(); + + return id; } public override void Construct(BitWriter writer) @@ -273,9 +263,11 @@ public override void Serialize(BitWriter writer) { writer.WriteBit(true); - writer.Write((uint) Items.Count); + var items = Items.Values.Where(k => k != default).ToList(); + + writer.Write((uint) items.Count); - foreach (var (_, item) in Items) + foreach (var item in items) { writer.Write(item.InventoryItemId); writer.Write(item.LOT); diff --git a/Uchu.World/Objects/Components/ReplicaComponents/PhantomPhysicsComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/PhantomPhysicsComponent.cs index 4689eb09..e692cc2c 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/PhantomPhysicsComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/PhantomPhysicsComponent.cs @@ -11,7 +11,7 @@ public class PhantomPhysicsComponent : ReplicaComponent public bool IsEffectActive { get; set; } - public uint EffectType { get; set; } + public PhantomPhysicsEffectType EffectType { get; set; } public float EffectAmount { get; set; } @@ -40,7 +40,7 @@ public override void Serialize(BitWriter writer) if (!writer.Flag(IsEffectActive)) return; - writer.Write(EffectType); + writer.Write((uint) EffectType); writer.Write(EffectAmount); if (writer.Flag(AffectedByDistance)) diff --git a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs index 19d1bf69..f03e647b 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/SkillComponent.cs @@ -53,22 +53,6 @@ protected SkillComponent() if (!GameObject.TryGetComponent(out var inventory)) return; _activeBehaviors.Add(BehaviorSlot.Primary, 1); - - Listen(inventory.OnEquipped, MountItem); - - Listen(inventory.OnUnEquipped, DismountItem); - - if (!(GameObject is Player player)) return; - - Listen(player.OnWorldLoad, async () => - { - if (!GameObject.TryGetComponent(out var manager)) return; - - foreach (var item in manager[InventoryType.Items].Items.Where(i => i.Equipped)) - { - await MountItem(item); - } - }); }); } @@ -81,50 +65,78 @@ public override void Serialize(BitWriter writer) { } - private async Task MountItem(Item item) + public async Task MountItemAsync(Lot item) { if (item == default) return; + await using var ctx = new CdClientContext(); + + var itemInfo = await ctx.ItemComponentTable.FirstOrDefaultAsync( + i => i.Id == item.GetComponentId(ComponentId.ItemComponent) + ); + + if (itemInfo == default) return; + + var slot = ((ItemType) (itemInfo.ItemType ?? 0)).GetBehaviorSlot(); + + RemoveSkill(slot); + await MountSkill(item); await EquipSkill(item); } - private async Task DismountItem(Item item) + public async Task DismountItemAsync(Lot item) { if (item == default) return; - var slot = item.ItemType.GetBehaviorSlot(); + await using var ctx = new CdClientContext(); + + var itemInfo = await ctx.ItemComponentTable.FirstOrDefaultAsync( + i => i.Id == item.GetComponentId(ComponentId.ItemComponent) + ); + + if (itemInfo == default) return; + + var slot = ((ItemType) (itemInfo.ItemType ?? 0)).GetBehaviorSlot(); RemoveSkill(slot); await DismountSkill(item); } - private async Task EquipSkill(Item item) + private async Task EquipSkill(Lot item) { if (item == default) return; - var infos = await BehaviorTree.GetSkillsForItem(item); + await using var ctx = new CdClientContext(); + + var itemInfo = await ctx.ItemComponentTable.FirstOrDefaultAsync( + i => i.Id == item.GetComponentId(ComponentId.ItemComponent) + ); + + if (itemInfo == default) return; + + var slot = ((ItemType) (itemInfo.ItemType ?? 0)).GetBehaviorSlot(); + + var infos = await BehaviorTree.GetSkillsForObject(item); var onUse = infos.FirstOrDefault(i => i.CastType == SkillCastType.OnUse); if (onUse == default) return; As().SendChatMessage($"Adding skill: {onUse.SkillId}"); - - var slot = item.ItemType.GetBehaviorSlot(); RemoveSkill(slot); SetSkill(slot, (uint) onUse.SkillId); } - private async Task MountSkill(Item item) + private async Task MountSkill(Lot item) { if (item == default) return; - var infos = await BehaviorTree.GetSkillsForItem(item); + var infos = await BehaviorTree.GetSkillsForObject(item); var onEquip = infos.FirstOrDefault(i => i.CastType == SkillCastType.OnEquip); @@ -132,18 +144,18 @@ private async Task MountSkill(Item item) As().SendChatMessage($"Mount skill: {onEquip.SkillId}"); - var tree = new BehaviorTree(item.Lot); + var tree = new BehaviorTree(item); await tree.BuildAsync(); await tree.MountAsync(GameObject); } - private async Task DismountSkill(Item item) + private async Task DismountSkill(Lot item) { if (item == default) return; - var infos = await BehaviorTree.GetSkillsForItem(item); + var infos = await BehaviorTree.GetSkillsForObject(item); var onEquip = infos.FirstOrDefault(i => i.CastType == SkillCastType.OnEquip); @@ -151,7 +163,7 @@ private async Task DismountSkill(Item item) As().SendChatMessage($"Dismount skill: {onEquip.SkillId}"); - var tree = new BehaviorTree(item.Lot); + var tree = new BehaviorTree(item); await tree.BuildAsync(); diff --git a/Uchu.World/Objects/Components/ReplicaComponents/TriggerComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/TriggerComponent.cs index 660b5388..4a2c9bbd 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/TriggerComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/TriggerComponent.cs @@ -1,5 +1,10 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; using InfectedRose.Triggers; using RakDotNet.IO; +using Uchu.Core; namespace Uchu.World { @@ -11,7 +16,7 @@ public class TriggerComponent : ReplicaComponent protected TriggerComponent() { - Listen(OnStart, () => + Listen(OnStart, async () => { if (!GameObject.Settings.TryGetValue("trigger_id", out var triggerIds)) return; @@ -20,21 +25,79 @@ protected TriggerComponent() if (split.Length != 2) return; - var triggerPrimaryId = int.Parse(split[0]); + var fileId = int.Parse(split[0]); var triggerId = int.Parse(split[1]); - foreach (var trigger in Zone.ZoneInfo.Triggers) + Trigger = Zone.ZoneInfo.TriggerDictionary[fileId, triggerId]; + + if (Trigger == default) { - // TODO: Primary id - if (trigger.Id != triggerPrimaryId || trigger.Id != triggerId) continue; + Logger.Error($"Failed to find trigger: {triggerId}:{fileId}"); - Trigger = trigger; + return; + } + + if (Trigger.Enabled == 0) return; + + foreach (var @event in Trigger.Events) + { + Logger.Debug($"TRIGGER EVENT: {@event.Id} -> {@event.Commands.FirstOrDefault()?.Id}"); - break; + switch (@event.Id) + { + case "OnCreate": + foreach (var command in @event.Commands) + { + await ExecuteTriggerCommand(command); + } + + break; + } } }); } + private async Task ExecuteTriggerCommand(TriggerCommand command) + { + switch (command.Id) + { + case "SetPhysicsVolumeEffect": + if (!GameObject.TryGetComponent(out var physicsComponent)) return; + + var arguments = command.Arguments.Split(','); + + physicsComponent.IsEffectActive = true; + + var effectTypeInfo = typeof(PhantomPhysicsEffectType); + + var effectType = (PhantomPhysicsEffectType) Enum.Parse(effectTypeInfo, arguments[0]); + + physicsComponent.EffectType = effectType; + + var amount = int.Parse(arguments[1]); + + physicsComponent.EffectAmount = amount; + + if (arguments.Length > 2) + { + var direction = new Vector3 + { + X = float.Parse(arguments[2]), + Y = float.Parse(arguments[2]), + Z = float.Parse(arguments[2]) + }; + + physicsComponent.EffectDirection = direction; + } + + Logger.Information($"PHYSICS: {physicsComponent.EffectType} -> {physicsComponent.EffectType} -> {physicsComponent.EffectDirection}"); + + break; + } + + GameObject.Serialize(GameObject); + } + public override void Construct(BitWriter writer) { Serialize(writer); diff --git a/Uchu.World/Objects/GameObjects/GameObject.cs b/Uchu.World/Objects/GameObjects/GameObject.cs index af109457..b22a577d 100644 --- a/Uchu.World/Objects/GameObjects/GameObject.cs +++ b/Uchu.World/Objects/GameObjects/GameObject.cs @@ -500,7 +500,7 @@ public static GameObject Instantiate(Type type, LevelObjectTemplate levelObject, // Check if this object is a trigger // - if (levelObject.LegoInfo.ContainsKey("trigger_id") && instance.GetComponent() == null) + if (levelObject.LegoInfo.ContainsKey("trigger_id")) { instance.AddComponent(); } diff --git a/Uchu.World/Objects/GameObjects/InstancingUtil.cs b/Uchu.World/Objects/GameObjects/InstancingUtil.cs index 03005914..88d9f572 100644 --- a/Uchu.World/Objects/GameObjects/InstancingUtil.cs +++ b/Uchu.World/Objects/GameObjects/InstancingUtil.cs @@ -33,6 +33,11 @@ public static GameObject Spawner(LevelObjectTemplate levelObject, Object parent) lot: levelObject.Lot ); + if (levelObject.LegoInfo.TryGetValue("trigger_id", out var trigger)) + { + Logger.Debug($"SPAWN TRIGGER: {trigger}"); + } + var spawnerComponent = instance.AddComponent(); spawnerComponent.Settings = levelObject.LegoInfo; diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index 32dddc7a..39a9f177 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Numerics; using System.Threading.Tasks; +using InfectedRose.Lvl; using Microsoft.EntityFrameworkCore; using RakDotNet; using RakDotNet.IO; @@ -280,36 +281,15 @@ internal static async Task ConstructAsync(Character character, IRakConne // Equip items // - var equippedItems = new Dictionary(); - - await using (var cdClient = new CdClientContext()) + foreach (var item in character.Items.Where(i => i.IsEquipped)) { - foreach (var item in character.Items.Where(i => i.IsEquipped)) - { - var cdClientObject = cdClient.ObjectsTable.FirstOrDefault( - o => o.Id == item.LOT - ); - - var itemRegistryEntry = cdClient.ComponentsRegistryTable.FirstOrDefault( - r => r.Id == item.LOT && r.Componenttype == 11 - ); - - if (cdClientObject == default || itemRegistryEntry == default) - { - Logger.Error($"{item.LOT} is not a valid item"); - continue; - } - - var itemComponent = cdClient.ItemComponentTable.First( - i => i.Id == itemRegistryEntry.Componentid - ); - - equippedItems.Add(itemComponent.EquipLocation, item); - } + await inventory.MountItemAsync( + item.LOT, + item.InventoryItemId, + LegoDataDictionary.FromString(item.ExtraInfo) + ); } - inventory.Items = equippedItems; - // // Register player gameobject in zone // diff --git a/Uchu.World/Uchu.World.csproj b/Uchu.World/Uchu.World.csproj index 1bd62f8e..fee7af0e 100644 --- a/Uchu.World/Uchu.World.csproj +++ b/Uchu.World/Uchu.World.csproj @@ -12,6 +12,7 @@ + From c5e37ada6b9435ec34bff46616a16e1eb4d0dfb0 Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 15 Feb 2020 21:58:46 +0100 Subject: [PATCH 20/23] Equipment refactor; control scheme swiching. --- Uchu.World/Behaviors/OverTimeBehavior.cs | 21 ++++++++++ Uchu.World/Enums/GameMessageId.cs | 3 +- .../Commands/CharacterCommandHandler.cs | 37 +++++++++++++++++ .../Handlers/GameMessages/InventoryHandler.cs | 12 ++---- .../Player/ModularBuilderComponent.cs | 8 ++-- .../ReplicaComponents/InventoryComponent.cs | 41 ++++++------------- .../Server/RocketLaunchpadComponent.cs | 2 +- Uchu.World/Objects/GameObjects/Item.cs | 4 ++ Uchu.World/Objects/GameObjects/Player.cs | 1 + Uchu.World/Objects/Zone.cs | 4 +- .../Server/SetPlayerControlSchemeMessage.cs | 25 +++++++++++ 11 files changed, 114 insertions(+), 44 deletions(-) create mode 100644 Uchu.World/Behaviors/OverTimeBehavior.cs create mode 100644 Uchu.World/Packets/GameMessages/Server/SetPlayerControlSchemeMessage.cs diff --git a/Uchu.World/Behaviors/OverTimeBehavior.cs b/Uchu.World/Behaviors/OverTimeBehavior.cs new file mode 100644 index 00000000..371b11ee --- /dev/null +++ b/Uchu.World/Behaviors/OverTimeBehavior.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; + +namespace Uchu.World.Behaviors +{ + public class OverTimeBehavior : BehaviorBase + { + public override BehaviorTemplateId Id => BehaviorTemplateId.OverTime; + + public BehaviorBase Action { get; set; } + + public override async Task BuildAsync() + { + Action = await GetBehavior("action"); + } + + public override async Task ExecuteAsync(ExecutionContext context, ExecutionBranchContext branchContext) + { + await Action.ExecuteAsync(context, branchContext); + } + } +} \ No newline at end of file diff --git a/Uchu.World/Enums/GameMessageId.cs b/Uchu.World/Enums/GameMessageId.cs index 3685c97a..e8715da3 100644 --- a/Uchu.World/Enums/GameMessageId.cs +++ b/Uchu.World/Enums/GameMessageId.cs @@ -294,6 +294,7 @@ public enum GameMessageId : ushort MarkInventoryItemAsActive = 0x6e7, // Undocumented? - StartArrangingWithModel = 0x483 + StartArrangingWithModel = 0x483, + SetPlayerControlScheme = 0x1a } } \ No newline at end of file diff --git a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs index 5079e100..f4678835 100644 --- a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs @@ -916,5 +916,42 @@ public string Inventory(string[] arguments, Player player) return $"Set inventory size to: {size}"; } + + [CommandHandler(Signature = "control", Help = "Change control scheme", GameMasterLevel = GameMasterLevel.Admin)] + public string Control(string[] arguments, Player player) + { + if (arguments.Length == default) return "control "; + + if (!int.TryParse(arguments[0], out var controlScheme)) return "Invalid "; + + player.Message(new SetPlayerControlSchemeMessage + { + Associate = player, + DelayCameraSwitchIfInCinematic = false, + SwitchCamera = true, + ControlScheme = controlScheme + }); + + if (arguments.Length > 1) + { + if (float.TryParse(arguments[1], out var timed)) + { + Task.Run(async () => + { + await Task.Delay((int) (timed * 1000)); + + player.Message(new SetPlayerControlSchemeMessage + { + Associate = player, + DelayCameraSwitchIfInCinematic = false, + SwitchCamera = true, + ControlScheme = 0 + }); + }); + } + } + + return $"Switched control scheme to: {controlScheme}"; + } } } \ No newline at end of file diff --git a/Uchu.World/Handlers/GameMessages/InventoryHandler.cs b/Uchu.World/Handlers/GameMessages/InventoryHandler.cs index 58f1e9c7..4b758d0f 100644 --- a/Uchu.World/Handlers/GameMessages/InventoryHandler.cs +++ b/Uchu.World/Handlers/GameMessages/InventoryHandler.cs @@ -44,21 +44,17 @@ public void RemoveItemHandler(RemoveItemToInventoryMessage message, Player playe public async Task EquipItemHandler(EquipItemMessage message, Player player) { if (message.Item == null) return; - - await player.GetComponent().EquipItemAsync(message.Item); + + await message.Item.EquipAsync(); } [PacketHandler] public async Task UnEquipItemHandler(UnEquipItemMessage message, Player player) { - var inventoryComponent = player.GetComponent(); - - Logger.Information($"UnEquip Item: {message.ItemToUnEquip} | {message.ReplacementItem}"); - - await inventoryComponent.UnEquipItemAsync(message.ItemToUnEquip); + await message.ItemToUnEquip.UnEquipAsync(); if (message.ReplacementItem != null) - await inventoryComponent.EquipItemAsync(message.ReplacementItem); + await message.ReplacementItem.EquipAsync(); } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Components/Player/ModularBuilderComponent.cs b/Uchu.World/Objects/Components/Player/ModularBuilderComponent.cs index 3a524888..d905289e 100644 --- a/Uchu.World/Objects/Components/Player/ModularBuilderComponent.cs +++ b/Uchu.World/Objects/Components/Player/ModularBuilderComponent.cs @@ -130,8 +130,8 @@ public async Task Pickup(Lot lot) var inventory = GameObject.GetComponent(); var item = inventory[InventoryType.TemporaryModels].Items.First(i => i.Lot == lot); - - await GameObject.GetComponent().EquipItemAsync(item); + + await item.EquipAsync(); /* As().Message(new StartArrangingWithItemMessage @@ -160,8 +160,8 @@ await inventory.MoveItemsBetweenInventoriesAsync( } var thinkingHat = inventory[InventoryType.Items].Items.First(i => i.Lot == 6086); - - await GameObject.GetComponent().UnEquipItemAsync(thinkingHat); + + await thinkingHat.UnEquipAsync(); IsBuilding = false; } diff --git a/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs index ea1bf8ce..b3dbdf26 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs @@ -44,24 +44,6 @@ protected InventoryComponent() foreach (var item in items) { - var cdClientObject = cdClient.ObjectsTable.FirstOrDefault( - o => o.Id == item.Itemid - ); - - var itemRegistryEntry = cdClient.ComponentsRegistryTable.FirstOrDefault( - r => r.Id == item.Itemid && r.Componenttype == 11 - ); - - if (cdClientObject == default || itemRegistryEntry == default) - { - Logger.Error($"{item.Itemid} is not a valid item"); - continue; - } - - var itemComponent = cdClient.ItemComponentTable.First( - i => i.Id == itemRegistryEntry.Componentid - ); - Debug.Assert(item.Itemid != null, "item.Itemid != null"); Debug.Assert(item.Count != null, "item.Count != null"); @@ -94,11 +76,9 @@ public async Task EquipItemAsync(Item item, bool ignoreAllChecks = false) Logger.Debug($"Equipping {item}"); - item.Equipped = true; - await OnEquipped.InvokeAsync(item); - await MountItemAsync(item.Lot, item.ObjectId, item.Settings); + await MountItemAsync(item.Lot, item.ObjectId, false, item.Settings); await ChangeEquippedSateOnPlayerAsync(item.ObjectId, true); @@ -113,7 +93,7 @@ public async Task UnEquipItemAsync(Item item) if (item != null) { - await MountItemAsync(item.Lot, 0); + await MountItemAsync(item.Lot, item.ObjectId, true); } } @@ -133,7 +113,7 @@ private async Task ChangeEquippedSateOnPlayerAsync(long itemId, bool equipped) await ctx.SaveChangesAsync(); } - private async Task GenerateProxyItemsAsync(Lot item) + private static async Task ParseProxyItemsAsync(Lot item) { await using var ctx = new CdClientContext(); @@ -153,8 +133,13 @@ private async Task GenerateProxyItemsAsync(Lot item) return proxies.ToArray(); } - public async Task MountItemAsync(Lot inventoryItem, long id, LegoDataDictionary settings = default) + public async Task MountItemAsync(Lot inventoryItem, long id, bool unEquip = false, LegoDataDictionary settings = default) { + if (Zone.TryGetGameObject(id, out var itemInstance)) + { + itemInstance.Equipped = !unEquip; + } + await using var ctx = new CdClientContext(); var itemInfo = await ctx.ItemComponentTable.FirstOrDefaultAsync( @@ -179,7 +164,7 @@ public async Task MountItemAsync(Lot inventoryItem, long id, LegoDataDicti if (item == default) goto equipItem; - await MountItemAsync(item.LOT, 0); + await MountItemAsync(item.LOT, item.InventoryItemId, true); goto equipItem; } @@ -198,14 +183,14 @@ public async Task MountItemAsync(Lot inventoryItem, long id, LegoDataDicti if (item == default) continue; - await MountItemAsync(item.LOT, 0); + await MountItemAsync(item.LOT, item.InventoryItemId, true); } } } equipItem: - if (id == default) + if (unEquip) { Items[location] = default; @@ -236,7 +221,7 @@ public async Task MountItemAsync(Lot inventoryItem, long id, LegoDataDicti * Equip proxies */ - var additionalItems = await GenerateProxyItemsAsync(inventoryItem); + var additionalItems = await ParseProxyItemsAsync(inventoryItem); if (additionalItems.Length <= 0) return id; diff --git a/Uchu.World/Objects/Components/Server/RocketLaunchpadComponent.cs b/Uchu.World/Objects/Components/Server/RocketLaunchpadComponent.cs index 3ad3dac5..ba11730a 100644 --- a/Uchu.World/Objects/Components/Server/RocketLaunchpadComponent.cs +++ b/Uchu.World/Objects/Components/Server/RocketLaunchpadComponent.cs @@ -33,7 +33,7 @@ public async Task OnInteract(Player player) return; } - await player.GetComponent().EquipItemAsync(rocket, true); + await rocket.EquipAsync(true); player.Message(new ChangeObjectWorldStateMessage { diff --git a/Uchu.World/Objects/GameObjects/Item.cs b/Uchu.World/Objects/GameObjects/Item.cs index 9b305613..f9de004d 100644 --- a/Uchu.World/Objects/GameObjects/Item.cs +++ b/Uchu.World/Objects/GameObjects/Item.cs @@ -110,6 +110,8 @@ public async Task EquipAsync(bool skipAllChecks = false) { var inventory = Player.GetComponent(); + Equipped = true; + await inventory.EquipItemAsync(this, skipAllChecks); } @@ -117,6 +119,8 @@ public async Task UnEquipAsync(bool skipAllChecks = false) { var inventory = Player.GetComponent(); + Equipped = false; + await inventory.UnEquipItemAsync(this); } diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index 39a9f177..c6e17b87 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -286,6 +286,7 @@ internal static async Task ConstructAsync(Character character, IRakConne await inventory.MountItemAsync( item.LOT, item.InventoryItemId, + false, LegoDataDictionary.FromString(item.ExtraInfo) ); } diff --git a/Uchu.World/Objects/Zone.cs b/Uchu.World/Objects/Zone.cs index 64c978c2..992a8cd2 100644 --- a/Uchu.World/Objects/Zone.cs +++ b/Uchu.World/Objects/Zone.cs @@ -255,8 +255,8 @@ public T GetGameObject(long objectId) where T : GameObject public bool TryGetGameObject(long objectId, out T result) where T : GameObject { - result = GameObjects.OfType().FirstOrDefault(o => o.ObjectId == objectId); - return result != default; + result = GameObjects.FirstOrDefault(o => o.ObjectId == objectId) as T; + return result != null; } #endregion diff --git a/Uchu.World/Packets/GameMessages/Server/SetPlayerControlSchemeMessage.cs b/Uchu.World/Packets/GameMessages/Server/SetPlayerControlSchemeMessage.cs new file mode 100644 index 00000000..5d05e16c --- /dev/null +++ b/Uchu.World/Packets/GameMessages/Server/SetPlayerControlSchemeMessage.cs @@ -0,0 +1,25 @@ +using RakDotNet.IO; + +namespace Uchu.World +{ + public class SetPlayerControlSchemeMessage : ServerGameMessage + { + public override GameMessageId GameMessageId => GameMessageId.SetPlayerControlScheme; + + public bool DelayCameraSwitchIfInCinematic { get; set; } + + public bool SwitchCamera { get; set; } + + public int ControlScheme { get; set; } + + public override void SerializeMessage(BitWriter writer) + { + writer.WriteBit(DelayCameraSwitchIfInCinematic); + + writer.WriteBit(SwitchCamera); + + if (writer.Flag(ControlScheme != 0)) + writer.Write(ControlScheme); + } + } +} \ No newline at end of file From bf2e5d6399ea6f303b60f135aa3279cc5c7af822 Mon Sep 17 00:00:00 2001 From: wincent Date: Tue, 18 Feb 2020 22:03:50 +0100 Subject: [PATCH 21/23] Added sqlite as a database provider. --- Uchu.Core/Database/Providers/SqliteContext.cs | 14 +++ Uchu.Core/Database/UchuContext.cs | 5 +- Uchu.Instance/Program.cs | 5 +- Uchu.Master/MasterServer.cs | 92 ++++++++++++------- .../Commands/CharacterCommandHandler.cs | 5 + .../ReplicaComponents/InventoryComponent.cs | 2 + .../Server/MissionGiverComponent.cs | 2 +- Uchu.World/Objects/Object.cs | 4 +- Uchu.World/Objects/Zone.cs | 2 - Uchu.World/Utilities/Event.cs | 2 + 10 files changed, 93 insertions(+), 40 deletions(-) create mode 100644 Uchu.Core/Database/Providers/SqliteContext.cs diff --git a/Uchu.Core/Database/Providers/SqliteContext.cs b/Uchu.Core/Database/Providers/SqliteContext.cs new file mode 100644 index 00000000..354edb38 --- /dev/null +++ b/Uchu.Core/Database/Providers/SqliteContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace Uchu.Core.Providers +{ + public class SqliteContext : UchuContextBase + { + public static string DatabasePath { get; set; } = "./Uchu.sqlite"; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite($"Data Source=\"{DatabasePath}\""); + } + } +} \ No newline at end of file diff --git a/Uchu.Core/Database/UchuContext.cs b/Uchu.Core/Database/UchuContext.cs index a14a6ffc..6fc412bb 100644 --- a/Uchu.Core/Database/UchuContext.cs +++ b/Uchu.Core/Database/UchuContext.cs @@ -1,7 +1,5 @@ using System; -using System.IO; using System.Threading.Tasks; -using System.Xml.Serialization; using Microsoft.EntityFrameworkCore; using Uchu.Core.Providers; @@ -47,6 +45,9 @@ public UchuContext() case "mysql": ContextBase = new MySqlContext(); break; + case "sqlite": + ContextBase = new SqliteContext(); + break; default: Logger.Error($"{config.Database.Provider} is a invalid or unsupported database provider"); throw new Exception($"Invalid database provider: \"{config.Database.Provider}\""); diff --git a/Uchu.Instance/Program.cs b/Uchu.Instance/Program.cs index ee1666d9..d2b565d4 100644 --- a/Uchu.Instance/Program.cs +++ b/Uchu.Instance/Program.cs @@ -21,7 +21,6 @@ private static async Task Main(string[] args) if (!Guid.TryParse(args[0], out var id)) throw new ArgumentException($"{args[0]} is not a valid GUID"); - var serializer = new XmlSerializer(typeof(Configuration)); @@ -35,6 +34,10 @@ private static async Task Main(string[] args) UchuContextBase.Config = (Configuration) serializer.Deserialize(fs); } + var masterPath = Path.GetDirectoryName(args[1]); + + SqliteContext.DatabasePath = Path.Combine(masterPath, "./Uchu.sqlite"); + ServerSpecification specification; await using (var ctx = new UchuContext()) diff --git a/Uchu.Master/MasterServer.cs b/Uchu.Master/MasterServer.cs index 1bc53d29..25def7b5 100644 --- a/Uchu.Master/MasterServer.cs +++ b/Uchu.Master/MasterServer.cs @@ -100,8 +100,6 @@ private static async Task HandleRequests() if (!Running) return; - await using var ctx = new UchuContext(); - // // Auto restart these // @@ -124,6 +122,9 @@ private static async Task HandleRequests() { // We don't auto restart world servers + + await using var ctx = new UchuContext(); + var specs = await ctx.Specifications.FirstOrDefaultAsync(s => s.Id == worldServer.Id); if (specs != default) @@ -137,7 +138,9 @@ private static async Task HandleRequests() } else { - var specifications = await ctx.Specifications.FirstAsync(w => w.Id == worldServer.Id); + await using var ctx = new UchuContext(); + + var specifications = await ctx.Specifications.FirstOrDefaultAsync(w => w.Id == worldServer.Id); if (specifications.ActiveUserCount != default) { @@ -157,16 +160,25 @@ private static async Task HandleRequests() } } - foreach (var request in ctx.WorldServerRequests) + WorldServerRequest[] requests; + + await using (var ctx = new UchuContext()) + { + requests = await ctx.WorldServerRequests.ToArrayAsync(); + } + + foreach (var request in requests) { if (request.State == WorldServerRequestState.Unanswered) { // // Search for available server // - + foreach (var worldServer in WorldServers.Where(w => w.ZoneId == request.ZoneId)) { + await using var ctx = new UchuContext(); + var specification = await ctx.Specifications.FirstAsync(s => s.Id == worldServer.Id); if (specification.ActiveUserCount >= specification.MaxUserCount) continue; @@ -186,7 +198,12 @@ private static async Task HandleRequests() // Start new server // - var clone = ctx.Specifications.Count(c => c.ZoneId == request.ZoneId); + int clone; + + await using (var ctx = new UchuContext()) + { + clone = await ctx.Specifications.CountAsync(c => c.ZoneId == request.ZoneId); + } int port; @@ -201,6 +218,8 @@ private static async Task HandleRequests() var ports = Config.Networking.WorldPorts.ToList(); + await using var ctx = new UchuContext(); + foreach (var specification in ctx.Specifications) { if (ports.Contains(specification.Port)) @@ -219,6 +238,8 @@ private static async Task HandleRequests() port = 2003; + await using var ctx = new UchuContext(); + while (ctx.Specifications.Any(s => s.Port == port)) { port++; @@ -228,38 +249,41 @@ private static async Task HandleRequests() // // Find request. // - - var serverRequest = await ctx.WorldServerRequests.FirstAsync( - r => r.Id == request.Id - ); - if (port == default) + await using (var ctx = new UchuContext()) { - // - // We were unable to find a user specified port. - // - - serverRequest.State = WorldServerRequestState.Error; - - await ctx.SaveChangesAsync(); - } - else - { - // - // Start the new server instance. - // - - serverRequest.SpecificationId = await StartWorld( - request.ZoneId, - (uint) clone, - default, - port + var serverRequest = await ctx.WorldServerRequests.FirstAsync( + r => r.Id == request.Id ); - serverRequest.State = WorldServerRequestState.Answered; - } + if (port == default) + { + // + // We were unable to find a user specified port. + // + + serverRequest.State = WorldServerRequestState.Error; - await ctx.SaveChangesAsync(); + await ctx.SaveChangesAsync(); + } + else + { + // + // Start the new server instance. + // + + serverRequest.SpecificationId = await StartWorld( + request.ZoneId, + (uint) clone, + default, + port + ); + + serverRequest.State = WorldServerRequestState.Answered; + } + + await ctx.SaveChangesAsync(); + } } continueToNext: ; @@ -269,6 +293,8 @@ private static async Task HandleRequests() private static async Task OpenConfig() { + SqliteContext.DatabasePath = Path.Combine(Directory.GetCurrentDirectory(), "./Uchu.sqlite"); + var serializer = new XmlSerializer(typeof(Configuration)); var fn = File.Exists("config.xml") ? "config.xml" : "config.default.xml"; diff --git a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs index f4678835..d63e671a 100644 --- a/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs +++ b/Uchu.World/Handlers/Commands/CharacterCommandHandler.cs @@ -275,6 +275,11 @@ public string Near(string[] arguments, Player player) info.Append($"\n: {property.Name} = {property.GetValue(component)}"); } + if (arguments.Contains("-in")) + { + current.OnInteract.Invoke(player); + } + finish: return info.ToString(); diff --git a/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs index b3dbdf26..66e64c91 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/InventoryComponent.cs @@ -49,6 +49,8 @@ protected InventoryComponent() await MountItemAsync(item.Itemid ?? 0, IdUtilities.GenerateObjectId()); } + + GameObject.Serialize(GameObject); }); } diff --git a/Uchu.World/Objects/Components/Server/MissionGiverComponent.cs b/Uchu.World/Objects/Components/Server/MissionGiverComponent.cs index 86df4197..5c6149e9 100644 --- a/Uchu.World/Objects/Components/Server/MissionGiverComponent.cs +++ b/Uchu.World/Objects/Components/Server/MissionGiverComponent.cs @@ -47,7 +47,7 @@ private void CollectMissions() if (quest == default) { - Logger.Warning($"{GameObject} has a Mission NPC Component with no corresponding quest: [{npcComponent.Id}] {npcComponent.MissionID}"); + Logger.Warning($"{GameObject} has a Mission NPC Component with no corresponding quest: \"[{GameObject.Lot}] {GameObject.Name}\" [{npcComponent.Id}] {npcComponent.MissionID}"); continue; } diff --git a/Uchu.World/Objects/Object.cs b/Uchu.World/Objects/Object.cs index d14423c7..94c5daba 100644 --- a/Uchu.World/Objects/Object.cs +++ b/Uchu.World/Objects/Object.cs @@ -70,7 +70,9 @@ public static void Destroy(Object obj) protected static void Update(Object obj) { - obj?.OnTick.Invoke(); + if (!obj.OnTick.Any) return; + + obj.OnTick.Invoke(); } } } \ No newline at end of file diff --git a/Uchu.World/Objects/Zone.cs b/Uchu.World/Objects/Zone.cs index 992a8cd2..a186827c 100644 --- a/Uchu.World/Objects/Zone.cs +++ b/Uchu.World/Objects/Zone.cs @@ -467,8 +467,6 @@ private Task ExecuteUpdateAsync() await Task.Delay(1000 / TicksPerSecondLimit); - var players = Players; - foreach (var obj in Objects) { try diff --git a/Uchu.World/Utilities/Event.cs b/Uchu.World/Utilities/Event.cs index 8255ca2f..e3429de7 100644 --- a/Uchu.World/Utilities/Event.cs +++ b/Uchu.World/Utilities/Event.cs @@ -14,6 +14,8 @@ public abstract class EventBase : EventBase where T : Delegate { protected T[] Actions = new T[0]; + public bool Any => Actions.Length != default; + internal void AddListener(T action) { Array.Resize(ref Actions, Actions.Length + 1); From 28b433d2a10dcbf78f8a91589f817adfea3f8647 Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Wed, 19 Feb 2020 09:37:39 +0100 Subject: [PATCH 22/23] Sqlite migrations --- .../20200219083714_SqliteInitial.Designer.cs | 513 ++++++++++++++++++ .../Sqlite/20200219083714_SqliteInitial.cs | 447 +++++++++++++++ .../Sqlite/SqliteContextModelSnapshot.cs | 511 +++++++++++++++++ 3 files changed, 1471 insertions(+) create mode 100644 Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.Designer.cs create mode 100644 Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.cs create mode 100644 Uchu.Core/Migrations/Sqlite/SqliteContextModelSnapshot.cs diff --git a/Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.Designer.cs b/Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.Designer.cs new file mode 100644 index 00000000..568eb848 --- /dev/null +++ b/Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.Designer.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Uchu.Core.Providers; + +namespace Uchu.Core.Migrations.Sqlite +{ + [DbContext(typeof(SqliteContext))] + [Migration("20200219083714_SqliteInitial")] + partial class SqliteInitial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.Property("CharacterId") + .ValueGeneratedOnAdd(); + + b.Property("BaseHealth"); + + b.Property("BaseImagination"); + + b.Property("Currency"); + + b.Property("CurrentArmor"); + + b.Property("CurrentHealth"); + + b.Property("CurrentImagination"); + + b.Property("CustomName") + .IsRequired() + .HasMaxLength(33); + + b.Property("EyeStyle"); + + b.Property("EyebrowStyle"); + + b.Property("FreeToPlay"); + + b.Property("GuildId"); + + b.Property("HairColor"); + + b.Property("HairStyle"); + + b.Property("InventorySize"); + + b.Property("LandingByRocket"); + + b.Property("LastActivity"); + + b.Property("LastClone"); + + b.Property("LastInstance"); + + b.Property("LastZone"); + + b.Property("LaunchedRocketFrom"); + + b.Property("Level"); + + b.Property("Lh"); + + b.Property("MaximumArmor"); + + b.Property("MaximumHealth"); + + b.Property("MaximumImagination"); + + b.Property("MouthStyle"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(33); + + b.Property("NameRejected"); + + b.Property("PantsColor"); + + b.Property("Rh"); + + b.Property("Rocket") + .HasMaxLength(30); + + b.Property("ShirtColor"); + + b.Property("ShirtStyle"); + + b.Property("TotalArmorPowerUpsCollected"); + + b.Property("TotalArmorRepaired"); + + b.Property("TotalBricksCollected"); + + b.Property("TotalCurrencyCollected"); + + b.Property("TotalDamageHealed"); + + b.Property("TotalDamageTaken"); + + b.Property("TotalDistanceDriven"); + + b.Property("TotalDistanceTraveled"); + + b.Property("TotalEnemiesSmashed"); + + b.Property("TotalFirstPlaceFinishes"); + + b.Property("TotalImaginationPowerUpsCollected"); + + b.Property("TotalImaginationRestored"); + + b.Property("TotalImaginationUsed"); + + b.Property("TotalLifePowerUpsCollected"); + + b.Property("TotalMissionsCompleted"); + + b.Property("TotalPetsTamed"); + + b.Property("TotalQuickBuildsCompleted"); + + b.Property("TotalRacecarBoostsActivated"); + + b.Property("TotalRacecarWrecks"); + + b.Property("TotalRacesFinished"); + + b.Property("TotalRacingImaginationCratesSmashed"); + + b.Property("TotalRacingImaginationPowerUpsCollected"); + + b.Property("TotalRacingSmashablesSmashed"); + + b.Property("TotalRocketsUsed"); + + b.Property("TotalSmashablesSmashed"); + + b.Property("TotalSuicides"); + + b.Property("TotalTimeAirborne"); + + b.Property("UniverseScore"); + + b.Property("UserId"); + + b.HasKey("CharacterId"); + + b.HasIndex("UserId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Uchu.Core.CharacterMail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AttachmentCount"); + + b.Property("AttachmentCurrency"); + + b.Property("AttachmentLot"); + + b.Property("AuthorId"); + + b.Property("Body"); + + b.Property("ExpirationTime"); + + b.Property("Read"); + + b.Property("RecipientId"); + + b.Property("SentTime"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("Mails"); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("FriendId"); + + b.Property("FriendTwoId"); + + b.Property("IsAccepted"); + + b.Property("IsBestFriend"); + + b.Property("IsDeclined"); + + b.Property("RequestHasBeenSent"); + + b.Property("RequestingBestFriend"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("FriendTwoId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("Uchu.Core.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RecipientId"); + + b.Property("SenderId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildInvites"); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.Property("InventoryItemId") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Count"); + + b.Property("ExtraInfo"); + + b.Property("InventoryType"); + + b.Property("IsBound"); + + b.Property("IsEquipped"); + + b.Property("LOT"); + + b.Property("Slot"); + + b.HasKey("InventoryItemId"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItems"); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("CompletionCount"); + + b.Property("LastCompletion"); + + b.Property("MissionId"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("Missions"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionId"); + + b.Property("TaskId"); + + b.HasKey("Id"); + + b.HasIndex("MissionId"); + + b.ToTable("MissionTasks"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("MissionTaskId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("MissionTaskId"); + + b.ToTable("MissionTaskValue"); + }); + + modelBuilder.Entity("Uchu.Core.ServerSpecification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ActiveUserCount"); + + b.Property("MaxUserCount"); + + b.Property("Port"); + + b.Property("ServerType"); + + b.Property("ZoneCloneId"); + + b.Property("ZoneId"); + + b.Property("ZoneInstanceId"); + + b.HasKey("Id"); + + b.ToTable("Specifications"); + }); + + modelBuilder.Entity("Uchu.Core.SessionCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Key"); + + b.Property("UserId"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("SessionCaches"); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("EmoteId"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("UnlockedEmote"); + }); + + modelBuilder.Entity("Uchu.Core.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd(); + + b.Property("Banned"); + + b.Property("BannedReason"); + + b.Property("CharacterIndex"); + + b.Property("CustomLockout"); + + b.Property("FirstTimeOnSubscription"); + + b.Property("FreeToPlay"); + + b.Property("GameMasterLevel"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(60); + + b.Property("Username") + .IsRequired() + .HasMaxLength(33); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Uchu.Core.WorldServerRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("SpecificationId"); + + b.Property("State"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("WorldServerRequests"); + }); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.HasOne("Uchu.Core.User", "User") + .WithMany("Characters") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.HasOne("Uchu.Core.Character", "FriendOne") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Uchu.Core.Character", "FriendTwo") + .WithMany() + .HasForeignKey("FriendTwoId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.HasOne("Uchu.Core.Guild", "Guild") + .WithMany("Invites") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Items") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Missions") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.HasOne("Uchu.Core.Mission", "Mission") + .WithMany("Tasks") + .HasForeignKey("MissionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.HasOne("Uchu.Core.MissionTask", "MissionTask") + .WithMany("Values") + .HasForeignKey("MissionTaskId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("UnlockedEmotes") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.cs b/Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.cs new file mode 100644 index 00000000..8a752931 --- /dev/null +++ b/Uchu.Core/Migrations/Sqlite/20200219083714_SqliteInitial.cs @@ -0,0 +1,447 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Uchu.Core.Migrations.Sqlite +{ + public partial class SqliteInitial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Guilds", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(nullable: true), + CreatorId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Guilds", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Mails", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Subject = table.Column(nullable: true), + Body = table.Column(nullable: true), + AttachmentLot = table.Column(nullable: false), + AttachmentCount = table.Column(nullable: false), + AttachmentCurrency = table.Column(nullable: false), + ExpirationTime = table.Column(nullable: false), + SentTime = table.Column(nullable: false), + Read = table.Column(nullable: false), + RecipientId = table.Column(nullable: false), + AuthorId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Mails", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SessionCaches", + columns: table => new + { + Id = table.Column(nullable: false), + Key = table.Column(nullable: true), + CharacterId = table.Column(nullable: false), + UserId = table.Column(nullable: false), + ZoneId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SessionCaches", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Specifications", + columns: table => new + { + Id = table.Column(nullable: false), + ServerType = table.Column(nullable: false), + Port = table.Column(nullable: false), + MaxUserCount = table.Column(nullable: false), + ActiveUserCount = table.Column(nullable: false), + ZoneId = table.Column(nullable: false), + ZoneCloneId = table.Column(nullable: false), + ZoneInstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Specifications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + UserId = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Username = table.Column(maxLength: 33, nullable: false), + Password = table.Column(maxLength: 60, nullable: false), + Banned = table.Column(nullable: false), + BannedReason = table.Column(nullable: true), + CustomLockout = table.Column(nullable: true), + GameMasterLevel = table.Column(nullable: false), + FreeToPlay = table.Column(nullable: false), + FirstTimeOnSubscription = table.Column(nullable: false), + CharacterIndex = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.UserId); + }); + + migrationBuilder.CreateTable( + name: "WorldServerRequests", + columns: table => new + { + Id = table.Column(nullable: false), + ZoneId = table.Column(nullable: false), + State = table.Column(nullable: false), + SpecificationId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WorldServerRequests", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GuildInvites", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SenderId = table.Column(nullable: false), + RecipientId = table.Column(nullable: false), + GuildId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuildInvites", x => x.Id); + table.ForeignKey( + name: "FK_GuildInvites_Guilds_GuildId", + column: x => x.GuildId, + principalTable: "Guilds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Characters", + columns: table => new + { + CharacterId = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 33, nullable: false), + CustomName = table.Column(maxLength: 33, nullable: false), + NameRejected = table.Column(nullable: false), + FreeToPlay = table.Column(nullable: false), + ShirtColor = table.Column(nullable: false), + ShirtStyle = table.Column(nullable: false), + PantsColor = table.Column(nullable: false), + HairStyle = table.Column(nullable: false), + HairColor = table.Column(nullable: false), + Lh = table.Column(nullable: false), + Rh = table.Column(nullable: false), + EyebrowStyle = table.Column(nullable: false), + EyeStyle = table.Column(nullable: false), + MouthStyle = table.Column(nullable: false), + LastZone = table.Column(nullable: false), + LastInstance = table.Column(nullable: false), + LastClone = table.Column(nullable: false), + LastActivity = table.Column(nullable: false), + Level = table.Column(nullable: false), + UniverseScore = table.Column(nullable: false), + Currency = table.Column(nullable: false), + MaximumHealth = table.Column(nullable: false), + CurrentHealth = table.Column(nullable: false), + BaseHealth = table.Column(nullable: false), + MaximumArmor = table.Column(nullable: false), + CurrentArmor = table.Column(nullable: false), + MaximumImagination = table.Column(nullable: false), + CurrentImagination = table.Column(nullable: false), + BaseImagination = table.Column(nullable: false), + TotalCurrencyCollected = table.Column(nullable: false), + TotalBricksCollected = table.Column(nullable: false), + TotalSmashablesSmashed = table.Column(nullable: false), + TotalQuickBuildsCompleted = table.Column(nullable: false), + TotalEnemiesSmashed = table.Column(nullable: false), + TotalRocketsUsed = table.Column(nullable: false), + TotalMissionsCompleted = table.Column(nullable: false), + TotalPetsTamed = table.Column(nullable: false), + TotalImaginationPowerUpsCollected = table.Column(nullable: false), + TotalLifePowerUpsCollected = table.Column(nullable: false), + TotalArmorPowerUpsCollected = table.Column(nullable: false), + TotalDistanceTraveled = table.Column(nullable: false), + TotalSuicides = table.Column(nullable: false), + TotalDamageTaken = table.Column(nullable: false), + TotalDamageHealed = table.Column(nullable: false), + TotalArmorRepaired = table.Column(nullable: false), + TotalImaginationRestored = table.Column(nullable: false), + TotalImaginationUsed = table.Column(nullable: false), + TotalDistanceDriven = table.Column(nullable: false), + TotalTimeAirborne = table.Column(nullable: false), + TotalRacingImaginationPowerUpsCollected = table.Column(nullable: false), + TotalRacingImaginationCratesSmashed = table.Column(nullable: false), + TotalRacecarBoostsActivated = table.Column(nullable: false), + TotalRacecarWrecks = table.Column(nullable: false), + TotalRacingSmashablesSmashed = table.Column(nullable: false), + TotalRacesFinished = table.Column(nullable: false), + TotalFirstPlaceFinishes = table.Column(nullable: false), + LandingByRocket = table.Column(nullable: false), + InventorySize = table.Column(nullable: false), + GuildId = table.Column(nullable: false), + LaunchedRocketFrom = table.Column(nullable: false), + Rocket = table.Column(maxLength: 30, nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Characters", x => x.CharacterId); + table.ForeignKey( + name: "FK_Characters_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Friends", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + IsAccepted = table.Column(nullable: false), + IsDeclined = table.Column(nullable: false), + IsBestFriend = table.Column(nullable: false), + RequestHasBeenSent = table.Column(nullable: false), + RequestingBestFriend = table.Column(nullable: false), + FriendId = table.Column(nullable: false), + FriendTwoId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Friends", x => x.Id); + table.ForeignKey( + name: "FK_Friends_Characters_FriendId", + column: x => x.FriendId, + principalTable: "Characters", + principalColumn: "CharacterId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Friends_Characters_FriendTwoId", + column: x => x.FriendTwoId, + principalTable: "Characters", + principalColumn: "CharacterId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "InventoryItems", + columns: table => new + { + InventoryItemId = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + LOT = table.Column(nullable: false), + Slot = table.Column(nullable: false), + Count = table.Column(nullable: false), + IsBound = table.Column(nullable: false), + IsEquipped = table.Column(nullable: false), + InventoryType = table.Column(nullable: false), + ExtraInfo = table.Column(nullable: true), + CharacterId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InventoryItems", x => x.InventoryItemId); + table.ForeignKey( + name: "FK_InventoryItems_Characters_CharacterId", + column: x => x.CharacterId, + principalTable: "Characters", + principalColumn: "CharacterId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Missions", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + MissionId = table.Column(nullable: false), + State = table.Column(nullable: false), + CompletionCount = table.Column(nullable: false), + LastCompletion = table.Column(nullable: false), + CharacterId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Missions", x => x.Id); + table.ForeignKey( + name: "FK_Missions_Characters_CharacterId", + column: x => x.CharacterId, + principalTable: "Characters", + principalColumn: "CharacterId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UnlockedEmote", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EmoteId = table.Column(nullable: false), + CharacterId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UnlockedEmote", x => x.Id); + table.ForeignKey( + name: "FK_UnlockedEmote_Characters_CharacterId", + column: x => x.CharacterId, + principalTable: "Characters", + principalColumn: "CharacterId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MissionTasks", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TaskId = table.Column(nullable: false), + MissionId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MissionTasks", x => x.Id); + table.ForeignKey( + name: "FK_MissionTasks_Missions_MissionId", + column: x => x.MissionId, + principalTable: "Missions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MissionTaskValue", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Value = table.Column(nullable: false), + Count = table.Column(nullable: false), + MissionTaskId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MissionTaskValue", x => x.Id); + table.ForeignKey( + name: "FK_MissionTaskValue_MissionTasks_MissionTaskId", + column: x => x.MissionTaskId, + principalTable: "MissionTasks", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Characters_UserId", + table: "Characters", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Friends_FriendId", + table: "Friends", + column: "FriendId"); + + migrationBuilder.CreateIndex( + name: "IX_Friends_FriendTwoId", + table: "Friends", + column: "FriendTwoId"); + + migrationBuilder.CreateIndex( + name: "IX_GuildInvites_GuildId", + table: "GuildInvites", + column: "GuildId"); + + migrationBuilder.CreateIndex( + name: "IX_InventoryItems_CharacterId", + table: "InventoryItems", + column: "CharacterId"); + + migrationBuilder.CreateIndex( + name: "IX_Missions_CharacterId", + table: "Missions", + column: "CharacterId"); + + migrationBuilder.CreateIndex( + name: "IX_MissionTasks_MissionId", + table: "MissionTasks", + column: "MissionId"); + + migrationBuilder.CreateIndex( + name: "IX_MissionTaskValue_MissionTaskId", + table: "MissionTaskValue", + column: "MissionTaskId"); + + migrationBuilder.CreateIndex( + name: "IX_UnlockedEmote_CharacterId", + table: "UnlockedEmote", + column: "CharacterId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Friends"); + + migrationBuilder.DropTable( + name: "GuildInvites"); + + migrationBuilder.DropTable( + name: "InventoryItems"); + + migrationBuilder.DropTable( + name: "Mails"); + + migrationBuilder.DropTable( + name: "MissionTaskValue"); + + migrationBuilder.DropTable( + name: "SessionCaches"); + + migrationBuilder.DropTable( + name: "Specifications"); + + migrationBuilder.DropTable( + name: "UnlockedEmote"); + + migrationBuilder.DropTable( + name: "WorldServerRequests"); + + migrationBuilder.DropTable( + name: "Guilds"); + + migrationBuilder.DropTable( + name: "MissionTasks"); + + migrationBuilder.DropTable( + name: "Missions"); + + migrationBuilder.DropTable( + name: "Characters"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Uchu.Core/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/Uchu.Core/Migrations/Sqlite/SqliteContextModelSnapshot.cs new file mode 100644 index 00000000..03a1e7be --- /dev/null +++ b/Uchu.Core/Migrations/Sqlite/SqliteContextModelSnapshot.cs @@ -0,0 +1,511 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Uchu.Core.Providers; + +namespace Uchu.Core.Migrations.Sqlite +{ + [DbContext(typeof(SqliteContext))] + partial class SqliteContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.Property("CharacterId") + .ValueGeneratedOnAdd(); + + b.Property("BaseHealth"); + + b.Property("BaseImagination"); + + b.Property("Currency"); + + b.Property("CurrentArmor"); + + b.Property("CurrentHealth"); + + b.Property("CurrentImagination"); + + b.Property("CustomName") + .IsRequired() + .HasMaxLength(33); + + b.Property("EyeStyle"); + + b.Property("EyebrowStyle"); + + b.Property("FreeToPlay"); + + b.Property("GuildId"); + + b.Property("HairColor"); + + b.Property("HairStyle"); + + b.Property("InventorySize"); + + b.Property("LandingByRocket"); + + b.Property("LastActivity"); + + b.Property("LastClone"); + + b.Property("LastInstance"); + + b.Property("LastZone"); + + b.Property("LaunchedRocketFrom"); + + b.Property("Level"); + + b.Property("Lh"); + + b.Property("MaximumArmor"); + + b.Property("MaximumHealth"); + + b.Property("MaximumImagination"); + + b.Property("MouthStyle"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(33); + + b.Property("NameRejected"); + + b.Property("PantsColor"); + + b.Property("Rh"); + + b.Property("Rocket") + .HasMaxLength(30); + + b.Property("ShirtColor"); + + b.Property("ShirtStyle"); + + b.Property("TotalArmorPowerUpsCollected"); + + b.Property("TotalArmorRepaired"); + + b.Property("TotalBricksCollected"); + + b.Property("TotalCurrencyCollected"); + + b.Property("TotalDamageHealed"); + + b.Property("TotalDamageTaken"); + + b.Property("TotalDistanceDriven"); + + b.Property("TotalDistanceTraveled"); + + b.Property("TotalEnemiesSmashed"); + + b.Property("TotalFirstPlaceFinishes"); + + b.Property("TotalImaginationPowerUpsCollected"); + + b.Property("TotalImaginationRestored"); + + b.Property("TotalImaginationUsed"); + + b.Property("TotalLifePowerUpsCollected"); + + b.Property("TotalMissionsCompleted"); + + b.Property("TotalPetsTamed"); + + b.Property("TotalQuickBuildsCompleted"); + + b.Property("TotalRacecarBoostsActivated"); + + b.Property("TotalRacecarWrecks"); + + b.Property("TotalRacesFinished"); + + b.Property("TotalRacingImaginationCratesSmashed"); + + b.Property("TotalRacingImaginationPowerUpsCollected"); + + b.Property("TotalRacingSmashablesSmashed"); + + b.Property("TotalRocketsUsed"); + + b.Property("TotalSmashablesSmashed"); + + b.Property("TotalSuicides"); + + b.Property("TotalTimeAirborne"); + + b.Property("UniverseScore"); + + b.Property("UserId"); + + b.HasKey("CharacterId"); + + b.HasIndex("UserId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Uchu.Core.CharacterMail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AttachmentCount"); + + b.Property("AttachmentCurrency"); + + b.Property("AttachmentLot"); + + b.Property("AuthorId"); + + b.Property("Body"); + + b.Property("ExpirationTime"); + + b.Property("Read"); + + b.Property("RecipientId"); + + b.Property("SentTime"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("Mails"); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("FriendId"); + + b.Property("FriendTwoId"); + + b.Property("IsAccepted"); + + b.Property("IsBestFriend"); + + b.Property("IsDeclined"); + + b.Property("RequestHasBeenSent"); + + b.Property("RequestingBestFriend"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("FriendTwoId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("Uchu.Core.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Guilds"); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RecipientId"); + + b.Property("SenderId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildInvites"); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.Property("InventoryItemId") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Count"); + + b.Property("ExtraInfo"); + + b.Property("InventoryType"); + + b.Property("IsBound"); + + b.Property("IsEquipped"); + + b.Property("LOT"); + + b.Property("Slot"); + + b.HasKey("InventoryItemId"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItems"); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("CompletionCount"); + + b.Property("LastCompletion"); + + b.Property("MissionId"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("Missions"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionId"); + + b.Property("TaskId"); + + b.HasKey("Id"); + + b.HasIndex("MissionId"); + + b.ToTable("MissionTasks"); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("MissionTaskId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("MissionTaskId"); + + b.ToTable("MissionTaskValue"); + }); + + modelBuilder.Entity("Uchu.Core.ServerSpecification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ActiveUserCount"); + + b.Property("MaxUserCount"); + + b.Property("Port"); + + b.Property("ServerType"); + + b.Property("ZoneCloneId"); + + b.Property("ZoneId"); + + b.Property("ZoneInstanceId"); + + b.HasKey("Id"); + + b.ToTable("Specifications"); + }); + + modelBuilder.Entity("Uchu.Core.SessionCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("Key"); + + b.Property("UserId"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("SessionCaches"); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CharacterId"); + + b.Property("EmoteId"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("UnlockedEmote"); + }); + + modelBuilder.Entity("Uchu.Core.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd(); + + b.Property("Banned"); + + b.Property("BannedReason"); + + b.Property("CharacterIndex"); + + b.Property("CustomLockout"); + + b.Property("FirstTimeOnSubscription"); + + b.Property("FreeToPlay"); + + b.Property("GameMasterLevel"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(60); + + b.Property("Username") + .IsRequired() + .HasMaxLength(33); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Uchu.Core.WorldServerRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("SpecificationId"); + + b.Property("State"); + + b.Property("ZoneId"); + + b.HasKey("Id"); + + b.ToTable("WorldServerRequests"); + }); + + modelBuilder.Entity("Uchu.Core.Character", b => + { + b.HasOne("Uchu.Core.User", "User") + .WithMany("Characters") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Friend", b => + { + b.HasOne("Uchu.Core.Character", "FriendOne") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Uchu.Core.Character", "FriendTwo") + .WithMany() + .HasForeignKey("FriendTwoId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.GuildInvite", b => + { + b.HasOne("Uchu.Core.Guild", "Guild") + .WithMany("Invites") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.InventoryItem", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Items") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.Mission", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("Missions") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTask", b => + { + b.HasOne("Uchu.Core.Mission", "Mission") + .WithMany("Tasks") + .HasForeignKey("MissionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.MissionTaskValue", b => + { + b.HasOne("Uchu.Core.MissionTask", "MissionTask") + .WithMany("Values") + .HasForeignKey("MissionTaskId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Uchu.Core.UnlockedEmote", b => + { + b.HasOne("Uchu.Core.Character", "Character") + .WithMany("UnlockedEmotes") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} From f009a4c5e1a4b6e6e39531ad458f7b829b490b74 Mon Sep 17 00:00:00 2001 From: wincent Date: Wed, 19 Feb 2020 15:49:23 +0100 Subject: [PATCH 23/23] Accommodated for standalone file publishing. --- Uchu.Core/Config/Configuration.cs | 2 + Uchu.Core/Uchu.Core.csproj | 1 + Uchu.Master/ManagedServer.cs | 10 +++-- Uchu.Master/MasterServer.cs | 21 ++++++----- Uchu.World/MissionSystem/MissionInstance.cs | 41 +++++++++------------ Uchu.World/Uchu.World.csproj | 1 - 6 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Uchu.Core/Config/Configuration.cs b/Uchu.Core/Config/Configuration.cs index d5488fd2..2853aa24 100644 --- a/Uchu.Core/Config/Configuration.cs +++ b/Uchu.Core/Config/Configuration.cs @@ -52,6 +52,8 @@ public class ServerDllSource [XmlElement] public string ServerDllSourcePath { get; set; } = "../../../../"; [XmlElement] public string DotNetPath { get; set; } = "dotnet"; + + [XmlElement] public string Instance { get; set; } = "Uchu.Instance.dll"; [XmlElement] public List ScriptDllSource { get; set; } = new List diff --git a/Uchu.Core/Uchu.Core.csproj b/Uchu.Core/Uchu.Core.csproj index 2615af9c..af0fd287 100644 --- a/Uchu.Core/Uchu.Core.csproj +++ b/Uchu.Core/Uchu.Core.csproj @@ -25,6 +25,7 @@ + diff --git a/Uchu.Master/ManagedServer.cs b/Uchu.Master/ManagedServer.cs index 158f2ef8..e37bf7b2 100644 --- a/Uchu.Master/ManagedServer.cs +++ b/Uchu.Master/ManagedServer.cs @@ -12,14 +12,18 @@ public class ManagedServer public ManagedServer(Guid id, string location, string dotnet) { + var useDotNet = !string.IsNullOrWhiteSpace(dotnet); + + var file = useDotNet ? dotnet : location; + Id = id; Process = new Process { StartInfo = { - FileName = dotnet, - WorkingDirectory = Path.GetDirectoryName(location), - Arguments = $"{Path.GetFileName(location)} {id} \"{MasterServer.ConfigPath}\"", + FileName = file, + WorkingDirectory = useDotNet ? Path.GetDirectoryName(location) : Directory.GetCurrentDirectory(), + Arguments = (useDotNet ? $"{Path.GetFileName(location)} " : "") + $"{id} \"{MasterServer.ConfigPath}\"", RedirectStandardOutput = false, UseShellExecute = true, CreateNoWindow = false, diff --git a/Uchu.Master/MasterServer.cs b/Uchu.Master/MasterServer.cs index 25def7b5..1463fc6b 100644 --- a/Uchu.Master/MasterServer.cs +++ b/Uchu.Master/MasterServer.cs @@ -350,27 +350,28 @@ private static async Task OpenConfig() var matchStr = NormalizePath("/bin/"); - var files = Directory.GetFiles(searchPath, "*.dll", SearchOption.AllDirectories) + var files = Directory.GetFiles(searchPath, "*", SearchOption.AllDirectories) .Select(Path.GetFullPath) .Where(f => f.Contains(matchStr)) // hacky solution .ToArray(); + var instance = string.IsNullOrWhiteSpace(Config.DllSource.Instance) + ? "Uchu.Instance.dll" + : Config.DllSource.Instance; + foreach (var file in files) { - switch (Path.GetFileName(file)) - { - case "Uchu.Instance.dll": - DllLocation = file; - break; - default: - continue; - } + if (Path.GetFileName(file) != instance) continue; + + DllLocation = file; + + break; } if (DllLocation == default) { throw new DllNotFoundException( - "Could not find DLL for Uchu.Instance. Did you forget to build it?" + $"Could not find DLL/EXE for {instance}. Did you forget to build it?" ); } diff --git a/Uchu.World/MissionSystem/MissionInstance.cs b/Uchu.World/MissionSystem/MissionInstance.cs index 214f255a..35b3eccc 100644 --- a/Uchu.World/MissionSystem/MissionInstance.cs +++ b/Uchu.World/MissionSystem/MissionInstance.cs @@ -306,48 +306,43 @@ public async Task SendRewardsAsync(int rewardItem) } var isMission = clientMission.IsMission ?? true; - - if (rewardItem <= 0) + + var isChoice = clientMission.IsChoiceReward ?? false; + + if (isChoice) + { + var (lot, count) = rewards.FirstOrDefault(l => l.Item1 == rewardItem); + + count = Math.Max(count, 1); + + Logger.Debug($"Choice: {lot}x{count} -> {rewardItem}"); + + var _ = Task.Run(async () => { await inventory.AddItemAsync(rewardItem, (uint) count); }); + } + else { foreach (var (rewardLot, rewardCount) in rewards) { var lot = rewardLot; var count = Math.Max(rewardCount, 1); - - if (lot == default) continue; + + if (lot <= 0) continue; if (isMission) { - var _ = Task.Run(async () => - { - await inventory.AddItemAsync(lot, (uint) count); - }); + var _ = Task.Run(async () => { await inventory.AddItemAsync(lot, (uint) count); }); } else { var _ = Task.Run(async () => { await Task.Delay(10000); - + await inventory.AddItemAsync(lot, (uint) count); }); } } } - else - { - var (lot, count) = rewards.FirstOrDefault(l => l.Item1 == rewardItem); - - count = Math.Max(count, 1); - - if (lot != default) - { - var _ = Task.Run(async () => - { - await inventory.AddItemAsync(lot, (uint) count); - }); - } - } } public async Task IsCompleteAsync() diff --git a/Uchu.World/Uchu.World.csproj b/Uchu.World/Uchu.World.csproj index fee7af0e..1bd62f8e 100644 --- a/Uchu.World/Uchu.World.csproj +++ b/Uchu.World/Uchu.World.csproj @@ -12,7 +12,6 @@ -