Skip to content

Commit

Permalink
feat(game): stones and aggro (#248)
Browse files Browse the repository at this point in the history
* feat(game): Implement /pull command

* feat(game): Implement /who command

* feat(game): Implement /forgetme command

* fix(game): Renamed /pull command to /aggregate

* feat(game): Implement /pull command

* fix(game): Renamed /guild_create command to /makeguild
fix(game): Renamed /guild_delete command to /deleteguild

* feat(game): Implement /weak and /weaken command

* feat(game): Implement /ipurge command

* feat(game): Implement /reset and /r command

* fix(game): disconnect would not remove logged in status

* fix(single): InMemoryRedis would calculate expiry wrong

* feat(game): Implement /dc command

* feat(game): Implement stones spawning mobs

* fix: build

* fix(game): null pointer when group.txt is missing

* fix: startup
  • Loading branch information
MeikelLP authored Dec 20, 2024
1 parent ec4db3c commit 52faf93
Show file tree
Hide file tree
Showing 27 changed files with 446 additions and 126 deletions.
78 changes: 47 additions & 31 deletions src/Core/Core/Utils/Math.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
namespace QuantumCore.Core.Utils
namespace QuantumCore.Core.Utils;

public static class MathUtils
{
public static class MathUtils
public static double Distance(int x1, int y1, int x2, int y2)
{
var a = x1 - x2;
var b = y1 - y2;

return Math.Sqrt(a * a + b * b);
}

/// <summary>
/// Returns the rotation in degrees
/// </summary>
public static double Rotation(double x, double y)
{
var vectorLength = Math.Sqrt(x * x + y * y);

var normalizedX = x / vectorLength;
var normalizedY = y / vectorLength;
var upVectorX = 0;
var upVectorY = 1;

var rotationRadians = -(Math.Atan2(normalizedY, normalizedX) - Math.Atan2(upVectorY, upVectorX));

var rotationDegress = rotationRadians * (180 / Math.PI);
if (rotationDegress < 0) rotationDegress += 360;
return rotationDegress;
}

public static int MinMax(int min, int value, int max)
{
var temp = (min > value ? min : value);
return (max < temp) ? max : temp;
}

public static (double X, double Y) GetDeltaByDegree(double degree)
{
var radian = DegreeToRadian(degree);

var x = Math.Sin(radian);
var y = Math.Cos(radian);
return (x, y);
}

public static double DegreeToRadian(double degree)
{
public static double Distance(int x1, int y1, int x2, int y2)
{
var a = x1 - x2;
var b = y1 - y2;

return Math.Sqrt(a * a + b * b);
}

public static double Rotation(double x, double y)
{
var vectorLength = Math.Sqrt(x * x + y * y);

var normalizedX = x / vectorLength;
var normalizedY = y / vectorLength;
var upVectorX = 0;
var upVectorY = 1;

var rotationRadians = -(Math.Atan2(normalizedY, normalizedX) - Math.Atan2(upVectorY, upVectorX));

var rotationDegress = rotationRadians * (180 / Math.PI);
if (rotationDegress < 0) rotationDegress += 360;
return rotationDegress;
}

public static int MinMax(int min, int value, int max)
{
var temp = (min > value ? min : value);
return (max < temp) ? max : temp;
}
return degree * Math.PI / 180.0;
}
}
3 changes: 2 additions & 1 deletion src/CorePluginAPI/Core/Models/MonsterData.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using BinarySerialization;
using QuantumCore.API.Game.Types;

namespace QuantumCore.API.Core.Models;

Expand Down Expand Up @@ -49,7 +50,7 @@ public class MonsterData
[FieldOrder(31)] public uint DropItemId { get; set; }
[FieldOrder(32)] public byte MountCapacity { get; set; }
[FieldOrder(33)] public byte OnClickType { get; set; }
[FieldOrder(34)] public byte Empire { get; set; }
[FieldOrder(34)] public EEmpire Empire { get; set; }

[FieldOrder(35)]
[FieldLength(65), FieldEncoding("ASCII")]
Expand Down
1 change: 1 addition & 0 deletions src/CorePluginAPI/Game/World/IEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public interface IEntity
public float Rotation { get; set; }
public IMap? Map { get; set; }
public byte HealthPercentage { get; }
public IEntity? Target { get; set; }
public List<IPlayerEntity> TargetedBy { get; }
public bool Dead { get; }

Expand Down
12 changes: 6 additions & 6 deletions src/Data/Core.Caching/InMemory/InMemoryRedisStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public IRedisListWrapper<T> CreateList<T>(string name)
else
{
Debug.Assert(value.Value.GetType().IsAssignableTo(typeof(IRedisListWrapper<T>)), "type mismatch");
return (IRedisListWrapper<T>) value.Value;
return (IRedisListWrapper<T>)value.Value;
}
}

Expand Down Expand Up @@ -55,7 +55,7 @@ public ValueTask<T> Get<T>(string key)
return default;
}

return ValueTask.FromResult((T) value.Value);
return ValueTask.FromResult((T)value.Value);
}

return default;
Expand Down Expand Up @@ -105,13 +105,13 @@ public ValueTask<long> Publish(string key, object obj)
var actionType = typeof(Action<>).MakeGenericType(obj.GetType());
var methodInfo = actionType.GetMethod(nameof(Action<object>.Invoke))!;

foreach (var action in (IEnumerable) callback)
foreach (var action in (IEnumerable)callback)
{
methodInfo.Invoke(action, [obj]);
}
}

return ValueTask.FromResult((long) callbacks.Length);
return ValueTask.FromResult((long)callbacks.Length);
}

public IRedisSubscriber Subscribe()
Expand Down Expand Up @@ -154,13 +154,13 @@ public void DelAllAsync(string pattern)

public ValueTask<long> Incr(string key)
{
if (!_dict.TryGetValue(key, out var tuple) || tuple.Expiry is not null && tuple.Expiry > DateTime.UtcNow)
if (!_dict.TryGetValue(key, out var tuple) || tuple.Expiry is not null && tuple.Expiry < DateTime.UtcNow)
{
// according to redis docs the value is set to 0 if it does not exist before incrementing
tuple = (0, null);
}

_dict[key] = ((int) tuple.Value + 1, tuple.Expiry);
_dict[key] = ((int)tuple.Value + 1, tuple.Expiry);
return ValueTask.FromResult(Convert.ToInt64(_dict[key].Value));
}

Expand Down
2 changes: 1 addition & 1 deletion src/Game.Benchmarks/Benchmarks/WorldUpdateBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void GlobalSetup()
provider.GetRequiredService<IDropProvider>(),
provider.GetRequiredService<IItemManager>(),
provider.GetRequiredService<IServerBase>(),
"test_map", new Coordinates(), 1024, 1024, null
"test_map", new Coordinates(), 1024, 1024, null, provider
)
});

Expand Down
18 changes: 18 additions & 0 deletions src/Libraries/Game.Server/Commands/AggregateCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using QuantumCore.API.Game;
using QuantumCore.Game.World.Entities;

namespace QuantumCore.Game.Commands;

[Command("aggregate", "Triggers all monsters around you to be aggro and target you")]
public class AggregateCommand : ICommandHandler
{
public Task ExecuteAsync(CommandContext context)
{
context.Player.ForEachNearbyEntity(e =>
{
if (e is not MonsterEntity) return;
e.Target = context.Player;
});
return Task.CompletedTask;
}
}
5 changes: 3 additions & 2 deletions src/Libraries/Game.Server/Commands/ClearInventoryCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace QuantumCore.Game.Commands
{
[Command("ip", "Clears inventory and equipped items")]
[Command("ipurge", "Clears inventory and equipped items")]
public class ClearInventoryCommand : ICommandHandler
{
private readonly ICacheManager _cacheManager;
Expand Down Expand Up @@ -33,11 +34,11 @@ public async Task ExecuteAsync(CommandContext ctx)
foreach (var item in items)
{
ctx.Player.RemoveItem(item);
ctx.Player.SendRemoveItem(item.Window, (ushort) item.Position);
ctx.Player.SendRemoveItem(item.Window, (ushort)item.Position);
await item.Destroy(_cacheManager);
}

ctx.Player.SendInventory();
}
}
}
}
20 changes: 20 additions & 0 deletions src/Libraries/Game.Server/Commands/ForgetMeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using QuantumCore.API.Game;
using QuantumCore.Game.World.Entities;

namespace QuantumCore.Game.Commands;

[Command("forgetme", "Clears aggro from all monsters targeting you")]
public class ForgetMeCommand : ICommandHandler
{
public Task ExecuteAsync(CommandContext context)
{
context.Player.ForEachNearbyEntity(e =>
{
if (e is MonsterEntity mob && mob.Target == context.Player)
{
mob.Target = null;
}
});
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace QuantumCore.Game.Commands.Guild;

[Command("guild_delete", "Delete guild")]
[Command("deleteguild", "Delete guild")]
public class GuildDeleteCommand : ICommandHandler<GuildDeleteCommandOptions>
{
private readonly IGuildManager _guildManager;
Expand Down Expand Up @@ -33,4 +33,4 @@ public async Task ExecuteAsync(CommandContext<GuildDeleteCommandOptions> context
public class GuildDeleteCommandOptions
{
[Value(0)] [Required] public string GuildName { get; set; } = "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace QuantumCore.Game.Commands.Guild;

[Command("guild_create", "Create guild")]
[Command("makeguild", "Create guild")]
[CommandNoPermission]
public class GuildCreateCommand : ICommandHandler<GuildCreateCommandOptions>
{
Expand Down Expand Up @@ -41,11 +41,7 @@ public async Task ExecuteAsync(CommandContext<GuildCreateCommandOptions> context
var guild = await _guildManager.CreateGuildAsync(context.Arguments.Name, player.Id);
foreach (var nearbyPlayer in context.Player.GetNearbyPlayers())
{
nearbyPlayer.Connection.Send(new GuildName
{
Id = guild.Id,
Name = guild.Name
});
nearbyPlayer.Connection.Send(new GuildName {Id = guild.Id, Name = guild.Name});
}

await context.Player.RefreshGuildAsync();
Expand All @@ -55,4 +51,4 @@ public async Task ExecuteAsync(CommandContext<GuildCreateCommandOptions> context
public class GuildCreateCommandOptions
{
[Value(0)] public string? Name { get; set; }
}
}
3 changes: 2 additions & 1 deletion src/Libraries/Game.Server/Commands/KickCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace QuantumCore.Game.Commands
{
[Command("kick", "Kick a player from the Server")]
[Command("dc", "Kick a player from the Server")]
public class KickCommand : ICommandHandler<KickCommandOptions>
{
private readonly IWorld _world;
Expand Down Expand Up @@ -41,4 +42,4 @@ public class KickCommandOptions
{
[Value(0, Required = true)] public string? Target { get; set; }
}
}
}
39 changes: 39 additions & 0 deletions src/Libraries/Game.Server/Commands/PullCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Numerics;
using QuantumCore.API.Game;
using QuantumCore.API.Game.World;
using QuantumCore.Core.Utils;

namespace QuantumCore.Game.Commands;

[Command("pull", "All monsters in range will quickly move towards you")]
[Command("pull_monster", "All monsters in range will quickly move towards you")]
public class PullCommand : ICommandHandler
{
public Task ExecuteAsync(CommandContext context)
{
const int maxDistance = 3000;
const int minDistance = 100;
var p = context.Player;
context.Player.ForEachNearbyEntity(e =>
{
if (e.Type == EEntityType.Monster)
{
var dist = Vector2.Distance(new Vector2(p.PositionX, p.PositionY),
new Vector2(e.PositionX, e.PositionY));
if (dist is > maxDistance or < minDistance)
return;

var degree = MathUtils.Rotation(p.PositionX - e.PositionX, p.PositionY - e.PositionY);

var pos = MathUtils.GetDeltaByDegree(degree);
var targetX = p.PositionX + pos.X + dist;
var targetY = p.PositionY + pos.Y + dist;
// not correct - moves to the wrong side of the player
// sadly my math skills are too low to implement it correctly
// good enough for now
e.Goto((int)targetX, (int)targetY);
}
});
return Task.CompletedTask;
}
}
17 changes: 17 additions & 0 deletions src/Libraries/Game.Server/Commands/ResetCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using QuantumCore.API.Game;

namespace QuantumCore.Game.Commands;

[Command("r", "Resets the players hp + sp to their max")]
[Command("reset", "Resets the players hp + sp to their max")]
public class ResetCommand : ICommandHandler
{
public Task ExecuteAsync(CommandContext context)
{
context.Player.Health = context.Player.Player.MaxHp;
context.Player.Mana = context.Player.Player.MaxSp;
context.Player.SendPoints();

return Task.CompletedTask;
}
}
8 changes: 5 additions & 3 deletions src/Libraries/Game.Server/Commands/SpawnCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ public class SpawnCommand : ICommandHandler<SpawnCommandOptions>
private readonly ILogger<SpawnCommand> _logger;
private readonly IItemManager _itemManager;
private readonly IDropProvider _dropProvider;
private readonly IServiceProvider _serviceProvider;

public SpawnCommand(IMonsterManager monsterManager, IAnimationManager animationManager, IWorld world,
ILogger<SpawnCommand> logger, IItemManager itemManager, IDropProvider dropProvider)
ILogger<SpawnCommand> logger, IItemManager itemManager, IDropProvider dropProvider, IServiceProvider serviceProvider)
{
_monsterManager = monsterManager;
_animationManager = animationManager;
_world = world;
_logger = logger;
_itemManager = itemManager;
_dropProvider = dropProvider;
_serviceProvider = serviceProvider;
}

public Task ExecuteAsync(CommandContext<SpawnCommandOptions> context)
Expand All @@ -54,7 +56,7 @@ public Task ExecuteAsync(CommandContext<SpawnCommandOptions> context)
}

// Create entity instance
var monster = new MonsterEntity(_monsterManager, _dropProvider, _animationManager, map, _logger,
var monster = new MonsterEntity(_monsterManager, _dropProvider, _animationManager, _serviceProvider, map, _logger,
_itemManager, context.Arguments.MonsterId, x, y);
_world.SpawnEntity(monster);
}
Expand All @@ -69,4 +71,4 @@ public class SpawnCommandOptions

[Value(1)] public uint Count { get; set; } = 1;
}
}
}
Loading

0 comments on commit 52faf93

Please sign in to comment.