AutoLoot / AutoCollect System for Vanosilla (WingsEmu / .NET 8)
Hello everyone,
today I'm sharing a simple
AutoLoot / AutoCollect system for
Vanosilla / WingsEmu.
With this feature enabled, normal items dropped by monsters are sent
directly to the player's inventory instead of being dropped on the ground.
Quest items are intentionally excluded and continue using the normal drop system.
Features
- Enable/disable AutoLoot individually for each player
- Normal monster drops go directly to the inventory
- Quest items are not affected
- Setting is stored on the character
- Persists between sessions
- Player command: $autoloot
- Shortcut: $al
- Default state is disabled
- PostgreSQL / EF Core support
Tested with:
- Vanosilla
- WingsEmu
- .NET 8
Files involved
The implementation touches or creates the following files:
Code:
srcs/WingsAPI.Game/Characters/IPlayerEntity.cs
srcs/WingsAPI.Game/Characters/PlayerEntity.cs
srcs/WingsAPI.Data/Character/CharacterDTO.cs
srcs/_plugins/WingsEmu.Plugins.BasicImplementation/PlayerEntityFactory.cs
srcs/_plugins/Plugin.DB.EF/Entities/PlayersData/DbCharacter.cs
srcs/_plugins/Plugin.DB.EF/Migrations/20260814000000_AddAutoLoot.cs
srcs/_plugins/Plugin.DB.EF/Migrations/GameContextModelSnapshot.cs
srcs/_plugins/WingsEmu.Plugins.BasicImplementation/Event/Items/DropItemEventHandler.cs
srcs/_plugins/WingsEmu.Plugins.Essentials/Player/AutoLootModule.cs
srcs/_plugins/WingsEmu.Plugins.Essentials/EssentialsPlugin.cs
1. IPlayerEntity.cs
Open:
Code:
srcs/WingsAPI.Game/Characters/IPlayerEntity.cs
Find:
[CODE=csharp]
bool QuickGetUp { get; set; }
[/CODE]
Add below it:
[CODE=csharp]
bool AutoLoot { get; set; }
[/CODE]
2. PlayerEntity.cs
Open:
Code:
srcs/WingsAPI.Game/Characters/PlayerEntity.cs
Part A - Add the property
Find:
[CODE=csharp]
public bool QuickGetUp { get; set; }
[/CODE]
Add below:
[CODE=csharp]
public bool AutoLoot { get; set; }
[/CODE]
Part B - Load AutoLoot when the character logs in
Find:
[CODE=csharp]
QuickGetUp = characterDto.QuickGetUp;
[/CODE]
Add below:
[CODE=csharp]
AutoLoot = characterDto.AutoLoot;
[/CODE]
3. CharacterDTO.cs
Open:
Code:
srcs/WingsAPI.Data/Character/CharacterDTO.cs
Find:
[CODE=csharp]
[ProtoMember(48)]
public bool QuickGetUp { get; set; }
[/CODE]
Add:
[CODE=csharp]
[ProtoMember(82)]
public bool AutoLoot { get; set; }
[/CODE]
Important:
Do not blindly use
82.
Check the highest ProtoMember number currently used in your version of the source and use the
next available number.
For example:
Code:
Highest ProtoMember = 81
AutoLoot = 82
Using an already existing ProtoMember number can cause serialization problems.
4. PlayerEntityFactory.cs
Open:
Code:
srcs/_plugins/WingsEmu.Plugins.BasicImplementation/PlayerEntityFactory.cs
Find:
[CODE=csharp]
QuickGetUp = playerEntity.QuickGetUp,
[/CODE]
Add below:
[CODE=csharp]
AutoLoot = playerEntity.AutoLoot,
[/CODE]
5. DbCharacter.cs
Open:
Code:
srcs/_plugins/Plugin.DB.EF/Entities/PlayersData/DbCharacter.cs
Find:
[CODE=csharp]
public bool QuickGetUp { get; set; }
[/CODE]
Add below:
[CODE=csharp]
public bool AutoLoot { get; set; }
[/CODE]
6. Create the EF Core migration
Create:
Code:
srcs/_plugins/Plugin.DB.EF/Migrations/20260814000000_AddAutoLoot.cs
Contents:
[CODE=csharp]
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Plugin.DB.EF.Migrations
{
public partial class AddAutoLoot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "AutoLoot",
table: "Characters",
type: "boolean",
nullable: false,
defaultValue: false);
}
```
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AutoLoot",
table: "Characters");
}
}
```
}
[/CODE]
This creates the
AutoLoot column in the
Characters table.
The default value is
false, so existing characters will have AutoLoot disabled by default.
7. Update GameContextModelSnapshot.cs
Open:
Code:
srcs/_plugins/Plugin.DB.EF/Migrations/GameContextModelSnapshot.cs
Find:
[CODE=csharp]
b.Property<bool>("QuickGetUp")
.HasColumnType("boolean");
[/CODE]
Add below:
[CODE=csharp]
b.Property<bool>("AutoLoot")
.HasColumnType("boolean");
[/CODE]
8. DropItemEventHandler.cs
Replace the complete contents of:
Code:
srcs/_plugins/WingsEmu.Plugins.BasicImplementation/Event/Items/DropItemEventHandler.cs
with:
[CODE=csharp]
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.Events;
using WingsAPI.Game.Extensions.ItemExtension.Inventory;
using WingsAPI.Game.Extensions.PacketGeneration;
using WingsEmu.Game;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Inventory.Event;
using WingsEmu.Game.Items;
using WingsEmu.Game.Maps;
using WingsEmu.Game.Raids;
namespace WingsEmu.Plugins.BasicImplementations.Event.Items;
public class ThrowItemEventHandler : IAsyncEventProcessor<ThrowItemEvent>
{
private readonly IGameItemInstanceFactory _gameItem;
private readonly IRandomGenerator _randomGenerator;
```
public ThrowItemEventHandler(
IGameItemInstanceFactory gameItem,
IRandomGenerator randomGenerator)
{
_gameItem = gameItem;
_randomGenerator = randomGenerator;
}
public async Task HandleAsync(
ThrowItemEvent e,
CancellationToken cancellation)
{
GameItemInstance newItem = _gameItem.CreateItem(
e.ItemVnum,
e.Quantity);
int rndX =
e.BattleEntity.PositionX +
_randomGenerator.RandomNumber(
e.MinimumDistance,
e.MaximumDistance + 1)
* (_randomGenerator.RandomNumber(0, 2) * 2 - 1);
int rndY =
e.BattleEntity.PositionY +
_randomGenerator.RandomNumber(
e.MinimumDistance,
e.MaximumDistance + 1)
* (_randomGenerator.RandomNumber(0, 2) * 2 - 1);
var position = new Position(
(short)rndX,
(short)rndY);
var item = new MonsterMapItem(
position.X,
position.Y,
newItem,
e.BattleEntity.MapInstance);
e.BattleEntity.MapInstance.AddDrop(item);
e.BattleEntity.BroadcastThrow(item);
}
```
}
public class DropItemEventHandler :
IAsyncEventProcessor<DropMapItemEvent>
{
private readonly IGameItemInstanceFactory _gameItem;
```
public DropItemEventHandler(
IGameItemInstanceFactory gameItem)
=> _gameItem = gameItem;
public async Task HandleAsync(
DropMapItemEvent e,
CancellationToken cancellation)
{
IMapInstance map = e.Map;
GameItemInstance newItem = _gameItem.CreateItem(
e.Vnum,
e.Amount,
(byte)e.Upgrade,
(sbyte)e.Rarity,
(byte)e.Design);
// AutoLoot:
// Skip the floor drop and send the item
// directly to the owner's inventory.
//
// Quest items are intentionally ignored.
if (e.OwnerId != -1 && !e.IsQuest)
{
IPlayerEntity owner =
map.GetCharacterById(e.OwnerId);
if (owner?.AutoLoot == true &&
owner.Session != null)
{
await owner.Session.AddNewItemToInventory(
newItem,
sendGiftIsFull: true);
return;
}
}
// Normal drop behavior
var item = new MonsterMapItem(
e.Position.X,
e.Position.Y,
newItem,
e.Map,
e.OwnerId,
e.IsQuest);
map.AddDrop(item);
item.BroadcastDrop();
}
```
}
[/CODE]
The important part is:
[CODE=csharp]
if (e.OwnerId != -1 && !e.IsQuest)
{
IPlayerEntity owner = map.GetCharacterById(e.OwnerId);
```
if (owner?.AutoLoot == true && owner.Session != null)
{
await owner.Session.AddNewItemToInventory(
newItem,
sendGiftIsFull: true);
return;
}
```
}
[/CODE]
When AutoLoot is enabled:
- The drop must have an owner
- The drop must not be a quest item
- The owner must have AutoLoot enabled
- The player must have an active session
If all conditions are met, the item is sent directly to the inventory and the normal floor drop is skipped.
Otherwise, the original drop behavior continues normally.
9. Create the AutoLoot player command
Create:
Code:
srcs/_plugins/WingsEmu.Plugins.Essentials/Player/AutoLootModule.cs
Contents:
[CODE=csharp]
using System.Threading.Tasks;
using Qmmands;
using WingsEmu.Commands.Entities;
using WingsEmu.Game.Extensions;
using WingsEmu.Game.Networking;
using WingsEmu.Packets.Enums.Chat;
namespace WingsEmu.Plugins.Essentials.Player;
[Name("AutoLoot")]
[Group("autoloot", "al")]
public class AutoLootModule : SaltyModuleBase
{
[Command("")]
public async Task<SaltyCommandResult> ToggleAutoLoot()
{
IClientSession session = Context.Player;
```
session.PlayerEntity.AutoLoot =
!session.PlayerEntity.AutoLoot;
string message = session.PlayerEntity.AutoLoot
? "[AutoLoot] Enabled - items will go directly to your inventory."
: "[AutoLoot] Disabled - items will drop on the ground normally.";
session.SendChatMessage(
message,
ChatMessageColorType.Green);
return new SaltyCommandResult(true);
}
```
}
[/CODE]
10. Register AutoLootModule
Open:
Code:
srcs/_plugins/WingsEmu.Plugins.Essentials/EssentialsPlugin.cs
At the top of the file add:
[CODE=csharp]
using WingsEmu.Plugins.Essentials.Player;
[/CODE]
Then, at the bottom of
OnLoad(), before its closing brace, add:
[CODE=csharp]
// Player features
_commands.AddModule<AutoLootModule>();
[/CODE]
Build
Compile WingsEmu:
[CODE=bash]
dotnet build WingsEmu.sln
[/CODE]
If the build succeeds, start the server normally.
EF Core will apply the migration when the server starts according to the normal Vanosilla database initialization process.
Usage
In-game:
or:
The command works as a toggle.
First use:
Code:
[AutoLoot] Enabled - items will go directly to your inventory.
Second use:
Code:
[AutoLoot] Disabled - items will drop on the ground normally.
Behavior
AutoLoot OFF
Code:
Monster dies
|
v
Item is generated
|
v
Item drops on the map
|
v
Player picks it up normally
AutoLoot ON
Code:
Monster dies
|
v
Item is generated
|
v
Check owner + AutoLoot
|
v
Send directly to inventory
|
v
Do not create the map drop
Quest item
Code:
Monster dies
|
v
Quest item generated
|
v
AutoLoot check ignored
|
v
Normal quest drop behavior
Important notes
- Quest drops are intentionally excluded.
- Do not reuse an existing ProtoMember number in CharacterDTO.
- The database column defaults to false.
- The feature is controlled individually by each character.
- If AutoLoot is disabled, the original WingsEmu drop behavior is preserved.
- Make a backup before modifying your source or database.
Credits
Implementation/tutorial prepared for the Vanosilla / WingsEmu community.
If you improve the implementation, find an incompatibility with another Vanosilla revision, or add additional AutoLoot options, feel free to share your changes in the thread.
Enjoy. 🙂