Repository navigation
Item Drops
Enemy rewards are created as ground items owned by the current room. The drop table selects an item ID and quantity, ItemCatalog creates the item models, and ItemFactory builds their world entities. The player then collects them through Item Pickup.
Gold and consumables can hold a quantity in one ground item. Charms are independent objects, so a reward containing two charms creates two separate entities. Drops are not added directly to the player's inventory when an enemy is defeated.
Defeat an enemy and collect its reward from the ground with E. Nearby NPC interaction takes priority over pickup. An active Magnet Potion can collect nearby Gold and consumables; charms still require normal pickup.
The default rules are defined in default-item-drops.json. Each configured enemy rule currently makes one roll. One roll selects either one item reward or no reward; it does not award Gold in addition to the selected item.
| Reward source | Current rule |
|---|---|
| Ordinary enemies | No empty outcome. Tables include Gold, all three healing sizes, Shield, Freeze Bomb, Burn Vial, Magnet Potion and at least one charm type. Gold quantity and the remaining item weights vary by enemy. |
| Boss types |
CERBERUS, DRAGON, SLEIPNIR, SNAKE_MINI_BOSS and FINAL_BOSS use the same reward distribution. All 13 built-in item types are available; a Gold result contains 2–5 Gold. |
| Final Boss explosive summon |
FINAL_BOSS_SUMMON has an 80% no-drop chance. The remaining outcomes include Gold, Small Health Potion, Shield, Freeze Bomb, Burn Vial, Magnet Potion and charms. |
| Unmatched enemy type | The general fallback table produces one Gold. |
The ordinary tables give Freeze Bomb a 25% chance, Burn Vial 5% and Magnet Potion 5%. The boss table gives them 15%, 5% and 5% respectively. These probabilities come from tables whose weights total 100; weights in custom tables do not have to total 100.
-
EnemyManagerComponenttracks a spawned enemy together with its loot type, normally the room configuration'sEnemyType.name(). - The manager listens for
entityDiedandfinalBossEncounterCompleted. Its active-enemy set accepts the defeat only once for that tracked entity. - It records the enemy's centre position and schedules drop creation through
EntityService. -
LootTable.forEnemy(enemyType)selects an exact matching override, or uses the general table when there is no match. An override replaces the general rule for that request. -
LootTable.roll(random)returnsItemDropSpecvalues.ItemCatalog.createItems(itemId, quantity)expands these into item models. -
ItemFactory.createEnemyDrops(enemyType, position)creates the entities and starts their drop animation. The room manager then registers and owns them.
The once-only guard applies to each tracked entity. Cerberus heads are individually tracked with the CERBERUS loot type, so this is not a single reward limit for an entire encounter. When a splitting enemy is replaced by children, the children inherit its loot type.
Final Boss explosive summons use a separate tracked set and the FINAL_BOSS_SUMMON rule. They do not count towards the active enemies that block room completion. A summon that reaches its death event can produce a reward; simply disposing a scripted entity does not request loot.
Drop creation is deferred so entities can be registered outside the current event or update operation. The manager checks whether it has been disposed before accepting or executing the scheduled work. Room disposal also disposes the ground items owned by that room.
The room's entity manager checks spawn positions against the terrain's world bounds. Ground items use the existing pickup and inventory paths described in Item Pickup.
LootTable loads the default JSON through defaultTable(). Its fields are:
| Field | Meaning |
|---|---|
rolls |
Number of independent selections. Zero disables rewards for that table. |
noDropWeight |
Relative weight of an empty result. Zero removes that outcome. |
entries |
Available item rewards. Each entry contains itemId, weight, minQuantity and maxQuantity. |
enemyRules |
Overrides containing an exact, case-sensitive enemyType and a nested table. |
For one roll, an entry's probability is its weight divided by the sum of all entry weights and noDropWeight. The chosen quantity is selected between minQuantity and maxQuantity, inclusive. Multiple rolls can select the same item more than once; the resulting specifications are not automatically merged.
For example, this custom table gives a 50% chance of no reward, a 40% chance of 1–3 Gold, and a 10% chance of one Small Health Potion:
{
"rolls": 1,
"noDropWeight": 50,
"entries": [
{ "itemId": "GOLD_COIN", "weight": 40, "minQuantity": 1, "maxQuantity": 3 },
{ "itemId": "HEALTH_POTION", "weight": 10, "minQuantity": 1, "maxQuantity": 1 }
],
"enemyRules": []
}Validation rejects unknown item IDs, negative roll or no-drop values, non-positive entry weights or quantities, inverted quantity ranges, and duplicate enemy overrides. A table with positive rolls must have a positive total weight within the supported integer range.
Code that already has the room entity can request a fixed reward through its existing EnemyManagerComponent:
EnemyManagerComponent items = roomEntity.getComponent(EnemyManagerComponent.class);
items.spawnItem(ItemIds.HEALTH_POTION, 2, new Vector2(x, y));
items.spawnItem(ItemIds.STRENGTH_CHARM, 2, new Vector2(x, y));The first call creates one potion item containing two units. The second creates two independent Strength Charms. spawnItem validates the ID and quantity, copies the position, and schedules creation and registration under the room. This explicit path does not roll the enemy table or apply the enemy reward's radial launch.
| Method | Responsibility |
|---|---|
EnemyManagerComponent.spawnItem(String itemId, int quantity, Vector2 position) |
Schedule a specific room-owned reward. |
new ItemFactory() |
Load and validate the default loot table. |
new ItemFactory(LootTable table, RandomGenerator random) |
Supply a table and random generator for reward selection. |
ItemFactory.createEnemyDrops(String enemyType, Vector2 position) |
Return unregistered reward entities with the enemy-drop launch applied. |
ItemFactory.createItem(Item item, Vector2 position) |
Return one unregistered world entity for an existing item model. |
LootTable.forEnemy(String enemyType) |
Resolve the exact override or general fallback. |
LootTable.roll(RandomGenerator random) |
Produce zero or more validated item specifications. |
Factory callers must provide the required services and loaded item textures. Creating an entity through the factory does not register it with EntityService or give it a room owner.
ItemFactory attaches physics, an item-layer hitbox, ItemComponent and ItemDropAnimationComponent. Enemy rewards start around the same visual centre and spread in different directions. Obstacle raycasts from the centre and sprite corners reduce the launch distance near terrain walls.
ItemDropAnimationComponent provides the visual arc, landing wobble and periodic glint. The vertical hop is a rendering offset; the item remains a ground entity for physics and pickup. Reward selection uses the factory's supplied random generator, while launch direction and speed use separate randomness.
The existing automated tests cover the following parts of the drop system:
| Test class | Coverage |
|---|---|
LootTableTest |
Default enemy rules, weight boundaries, no-drop outcomes, quantities, fallback selection and invalid configuration. |
ItemDropSpecTest and ItemCatalogTest
|
Valid reward specifications, stack quantities and independent charm instances. |
ItemFactoryTest |
Item components and positions, reward creation, radial movement and nearby terrain walls. |
ItemDropAnimationComponentTest |
Drop animation progression and timing. |
EnemyManagerComponentTest and EntityManagerComponentTest
|
Room-owned spawning, enemy tracking and lifecycle behaviour. |
EnemyLootPickupIntegrationTest |
Registered rewards passing through real ground pickup and inventory handling without duplicate collection. |
Entities and Components
Input Handling
- Service Locator
- Loading Resources
- Logging
- Unit Testing
- Debug Terminal
- Animations
- Audio
- AI
- Physics
- Terrain
- Concurrency & Threading
- Settings
Components
Achievements
Player
Items and Inventory
Enemies
Bosses
- Mini Boss 1: Cerberus (Egyptian)
- Mini Boss 2: Snake (Greek)
- Mini Boss 2: Snake Remake
- Mini Boss 3: Chinese Dragon
- Mini Boss 4: Sleipnir (Norse)
- Final Boss: Grandpa (Stage 2 Remake and Stage 3 Improvements)