Skip to content

Commit 8a51ef5

Browse files
authored
Implement IEntitySpawner and some AI beginning. (#471)
* Some very basic head tracking. Not really using targets yet * Basics for EntityFactory, fix some issues with spawning pigs * Create (I)EntitySpawner also accessible through plugins ;) * Add more methods to EntitySpawner * Add missing methods to IEntitySpawner * More things to play with
1 parent 0cd6390 commit 8a51ef5

File tree

12 files changed

+314
-38
lines changed

12 files changed

+314
-38
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Obsidian.API.Entities;
8+
9+
/// <summary>
10+
/// Can be used to spawn entities. Can be retrieved from <see cref="IWorld.GetNewEntitySpawner"/>.
11+
/// </summary>
12+
public interface IEntitySpawner
13+
{
14+
/// <summary>
15+
/// Sets the entity type to spawn.
16+
/// </summary>
17+
/// <param name="type">The entity type to spawn</param>
18+
/// <returns></returns>
19+
IEntitySpawner WithEntityType(EntityType type);
20+
21+
/// <summary>
22+
/// Whether this entity is a baby entity. Only applicable to Ageable entities.
23+
/// </summary>
24+
/// <returns></returns>
25+
IEntitySpawner AsBaby();
26+
27+
/// <summary>
28+
/// Position to spawn the entity at.
29+
/// </summary>
30+
/// <param name="position">Entity position</param>
31+
/// <returns></returns>
32+
IEntitySpawner AtPosition(VectorF position);
33+
34+
/// <summary>
35+
/// Spawns the entity with a custom name plate.
36+
/// </summary>
37+
/// <param name="name">The name this entity has.</param>
38+
/// <param name="visible">Whether the name is visible.</param>
39+
/// <returns></returns>
40+
IEntitySpawner WithCustomName(string name, bool visible = true);
41+
42+
/// <summary>
43+
/// Sets whether this entity has an ambient potion effect.
44+
/// </summary>
45+
/// <param name="ambient">Whether this entity has the ambient potion effect</param>
46+
/// <returns></returns>
47+
IEntitySpawner WithAmbientPotionEffect(bool ambient);
48+
49+
/// <summary>
50+
/// Sets the amount of arrows this entity has absorbed.
51+
/// </summary>
52+
/// <param name="arrows">The amount of arrows absorbed</param>
53+
/// <returns></returns>
54+
IEntitySpawner WithAbsorbedArrows(int arrows);
55+
56+
/// <summary>
57+
/// Sets the amount of absorbed stingers this entity has.
58+
/// </summary>
59+
/// <param name="stingers">Amount of absorbed stingers</param>
60+
/// <returns></returns>
61+
IEntitySpawner WithAbsorbedStingers(int stingers);
62+
63+
/// <summary>
64+
/// Makes the entity spawn burning.
65+
/// </summary>
66+
/// <returns></returns>
67+
IEntitySpawner IsBurning();
68+
69+
/// <summary>
70+
/// Makes the entity spawn glowing.
71+
/// </summary>
72+
/// <returns></returns>
73+
IEntitySpawner IsGlowing();
74+
75+
/// <summary>
76+
/// Spawns the entity.
77+
/// </summary>
78+
/// <returns>The newly spawned entity. Can be cast to the apropriate type.</returns>
79+
IEntity Spawn();
80+
}

Obsidian.API/_Interfaces/IEntity.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public interface IEntity
3030

3131
public string? TranslationKey { get; }
3232

33-
public bool CustomNameVisible { get; }
33+
public bool CustomNameVisible { get; set; }
3434
public bool Silent { get; }
3535
public bool NoGravity { get; }
3636
public MovementFlags MovementFlags { get; }

Obsidian.API/_Interfaces/IWorld.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Obsidian.API.Entities;
2+
13
namespace Obsidian.API;
24

35
public interface IWorld : IAsyncDisposable
@@ -18,6 +20,8 @@ public interface IWorld : IAsyncDisposable
1820
public int LoadedChunkCount { get; }
1921
public int ChunksToGenCount { get; }
2022

23+
public IEntitySpawner GetNewEntitySpawner();
24+
2125
public ValueTask<IBlock?> GetBlockAsync(Vector location);
2226
public ValueTask<IBlock?> GetBlockAsync(int x, int y, int z);
2327
public ValueTask SetBlockAsync(Vector location, IBlock block);

Obsidian/Commands/Modules/MainCommandModule.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Obsidian.API.Utilities;
44
using Obsidian.Commands.Framework.Entities;
55
using Obsidian.Entities;
6+
using Obsidian.Entities.Factories;
67
using Obsidian.Net.Packets.Play.Clientbound;
78
using Obsidian.Registries;
89
using Obsidian.WorldData;
@@ -362,12 +363,16 @@ public async Task SpawnEntityAsync(string entityType)
362363
return;
363364
}
364365

365-
player.World.SpawnEntity(player.Position, type);
366+
var builder = player.World.GetNewEntitySpawner()
367+
.WithEntityType(type)
368+
.AtPosition(player.Position)
369+
.Spawn();
370+
366371
await player.SendMessageAsync($"Spawning: {type}");
367372
}
368373

369374
[Command("derp")]
370-
[CommandInfo("derpy derp", "/derp")]
375+
[CommandInfo("derpy derp spawns a derp", "/derp [entity_type]")]
371376
[IssuerScope(CommandIssuers.Client)]
372377
public async Task DerpAsync(string entityType)
373378
{
@@ -381,7 +386,17 @@ public async Task DerpAsync(string entityType)
381386
return;
382387
}
383388

384-
var frogge = player.World.SpawnEntity(player.Position, type);
389+
var frogge = player.World.GetNewEntitySpawner()
390+
.WithEntityType(type)
391+
.AtPosition(player.Position)
392+
.WithCustomName("Derpy Derp")
393+
.AsBaby()
394+
.IsBurning()
395+
.IsGlowing()
396+
.WithAbsorbedArrows(50)
397+
.WithAbsorbedStingers(50)
398+
.WithAmbientPotionEffect(true)
399+
.Spawn();
385400
var server = (this.Server as Server)!;
386401

387402
_ = Task.Run(async () =>

Obsidian/Entities/AgeableMob.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public override void Write(INetStreamWriter writer)
88
{
99
base.Write(writer);
1010

11-
writer.WriteEntityMetadataType(15, EntityMetadataType.Boolean);
11+
writer.WriteEntityMetadataType(16, EntityMetadataType.Boolean);
1212
writer.WriteBoolean(IsBaby);
1313
}
1414
}

Obsidian/Entities/Animal.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1-
namespace Obsidian.Entities;
1+

2+
3+
namespace Obsidian.Entities;
24

35
public class Animal : AgeableMob
46
{
7+
public async override ValueTask TickAsync()
8+
{
9+
// TODO obby doesn't properly spawn entities yet
10+
var players = world.PlayersInRange((Vector)Position);
11+
if (players.Any())
12+
{
13+
var closest = players.OrderBy(p => VectorF.Distance(Position, p.Position)).First();
14+
var closestPosition = new VectorF()
15+
{
16+
X = closest.Position.X,
17+
Y = (float)closest.HeadY,
18+
Z = closest.Position.Z
19+
};
20+
21+
var lookAt = closestPosition - Position;
22+
23+
var yaw = (byte)((MathF.Atan2(lookAt.Z, lookAt.X) * (256 / (2 * MathF.PI)) - 64) % 256);
24+
var pitch = (byte)(256 - (MathF.Asin(lookAt.Y / lookAt.Magnitude) * (256 / (2 * MathF.PI))));
25+
26+
SetRotation(new Angle(yaw), new Angle(pitch), MovementFlags.OnGround);
27+
SetHeadRotation(new Angle(yaw));
28+
}
29+
30+
await base.TickAsync();
31+
}
532
}

Obsidian/Entities/Entity.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class Entity : IEquatable<Entity>, IEntity
4141

4242
public int Air { get; set; } = 300;
4343

44-
public float Health { get; set; }
44+
public float Health { get; set; } = 100;
4545

4646
public ChatMessage? CustomName { get; set; }
4747

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using Obsidian.API.Entities;
2+
using Obsidian.Services;
3+
using Obsidian.WorldData;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Obsidian.Entities.Factories;
11+
internal class EntitySpawner : IEntitySpawner
12+
{
13+
private IWorld world;
14+
15+
private EntityType? entityType = null;
16+
17+
private VectorF position = VectorF.Zero;
18+
private bool isBaby = false;
19+
private string? customName = null;
20+
private bool customNameVisible = false;
21+
private bool ambientPotionEffect = false;
22+
private int absorbedArrows = 0;
23+
private int absorbedStingers = 0;
24+
private bool burning = false;
25+
private bool glowing = false;
26+
27+
public EntitySpawner(IWorld world)
28+
{
29+
this.world = world;
30+
}
31+
32+
public IEntitySpawner WithEntityType(EntityType type)
33+
{
34+
entityType = type;
35+
return this;
36+
}
37+
38+
public IEntitySpawner AsBaby()
39+
{
40+
isBaby = true;
41+
return this;
42+
}
43+
44+
public IEntitySpawner AtPosition(VectorF position)
45+
{
46+
this.position = position;
47+
return this;
48+
}
49+
50+
public IEntitySpawner WithCustomName(string name, bool visible = true)
51+
{
52+
customName = name;
53+
customNameVisible = visible;
54+
return this;
55+
}
56+
57+
public IEntitySpawner WithAmbientPotionEffect(bool ambient)
58+
{
59+
ambientPotionEffect = ambient;
60+
return this;
61+
}
62+
63+
public IEntitySpawner WithAbsorbedArrows(int arrows)
64+
{
65+
absorbedArrows = arrows;
66+
return this;
67+
}
68+
69+
public IEntitySpawner WithAbsorbedStingers(int stingers)
70+
{
71+
absorbedStingers = stingers;
72+
return this;
73+
}
74+
75+
public IEntitySpawner IsBurning()
76+
{
77+
this.burning = true;
78+
return this;
79+
}
80+
81+
public IEntitySpawner IsGlowing()
82+
{
83+
this.glowing = true;
84+
return this;
85+
}
86+
87+
public IEntity Spawn()
88+
{
89+
var packetBroadcaster = (world as World).PacketBroadcaster;
90+
91+
// This could get sgen'd, same for the entity classes. but for now, this is fine for implementation
92+
Entity entity = entityType switch
93+
{
94+
EntityType.Pig => new Pig()
95+
{
96+
PacketBroadcaster = packetBroadcaster,
97+
World = world,
98+
},
99+
EntityType.Horse => new Horse()
100+
{
101+
PacketBroadcaster = packetBroadcaster,
102+
World = world,
103+
},
104+
EntityType.Llama => new Llama()
105+
{
106+
PacketBroadcaster = packetBroadcaster,
107+
World = world,
108+
},
109+
EntityType.Donkey => new Donkey()
110+
{
111+
PacketBroadcaster = packetBroadcaster,
112+
World = world
113+
},
114+
EntityType.SkeletonHorse => new SkeletonHorse()
115+
{
116+
PacketBroadcaster = packetBroadcaster,
117+
World = world
118+
},
119+
EntityType.ZombieHorse => new ZombieHorse()
120+
{
121+
PacketBroadcaster = packetBroadcaster,
122+
World = world
123+
},
124+
125+
null => throw new InvalidOperationException("Entity type must be set"),
126+
127+
_ => entityType.Value.IsNonLiving() ?
128+
new Entity()
129+
{
130+
PacketBroadcaster = packetBroadcaster,
131+
World = world,
132+
} :
133+
new Living()
134+
{
135+
PacketBroadcaster = packetBroadcaster,
136+
World = world
137+
}
138+
};
139+
140+
entity.Type = entityType.Value;
141+
entity.EntityId = Server.GetNextEntityId();
142+
entity.Position = position;
143+
144+
if (entity is Living living && customName != null)
145+
{
146+
living.CustomName = customName;
147+
living.CustomNameVisible = customNameVisible;
148+
living.AmbientPotionEffect = ambientPotionEffect;
149+
living.AbsorbedArrows = absorbedArrows;
150+
living.AbsorbedStingers = absorbedStingers;
151+
}
152+
153+
if (entity is AgeableMob ageable && isBaby)
154+
{
155+
ageable.IsBaby = isBaby;
156+
}
157+
158+
entity.Burning = burning;
159+
entity.Glowing = glowing;
160+
161+
return (world as World).SpawnEntity(entity);
162+
}
163+
}

Obsidian/Entities/Mob.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public override void Write(INetStreamWriter writer)
88
{
99
base.Write(writer);
1010

11-
writer.WriteEntityMetadataType(14, EntityMetadataType.Byte);
11+
writer.WriteEntityMetadataType(15, EntityMetadataType.Byte);
1212
writer.WriteByte((byte)MobBitMask);
1313
}
1414
}

Obsidian/Entities/Pig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ public override void Write(INetStreamWriter writer)
1111
{
1212
base.Write(writer);
1313

14-
writer.WriteEntityMetadataType(16, EntityMetadataType.Boolean);
14+
writer.WriteEntityMetadataType(17, EntityMetadataType.Boolean);
1515
writer.WriteBoolean(HasSaddle);
1616

17-
writer.WriteEntityMetadataType(17, EntityMetadataType.VarInt);
17+
writer.WriteEntityMetadataType(18, EntityMetadataType.VarInt);
1818
writer.WriteVarInt(TotalTimeBoost);
1919
}
2020
}

0 commit comments

Comments
 (0)