Item Drop, Pickup and Strength Charm — Sprint 1 - UQcsse3200/2026-studio-4 GitHub Wiki
Historical Sprint 1 record. The implementation notes, screenshots and test numbers below describe their dated Sprint 1 baselines, not the current game. PR #88 has since merged (commit
191c4c8, included in checked main9625e56); old “OPEN / pending merge” statements below are retained only as historical evidence. Current items uses generic ItemPickupComponent, normal Gold/consumable rewards and no Charm-count control in the shared HUD. Current StrengthCharm adds +10 per distinct picked-up Charm object, with only same-object duplicate application guarded; Charm.drop reverses its bonus, while removeCharm alone does not. The non-stacking rule below is historical. See current Sprint 2 documentation and current evidence.
Items, Inventory, Pickup and Strength Charm
Verification basis: production code in main and the PR #88 test evidence.
Current production baseline:
main@7632062. Verification candidate:items@f2d45de, checked on 3 September 2026 (Australia/Brisbane). The test/documentation changes in PR #88 are not yet in main.
Overview
The Sprint 1 item system provides a Strength Charm world entity, collision-and-E-key pickup, Charm inventory storage, a non-stacking +10 baseAttack effect, and HUD feedback.
The production Enemy death → Strength Charm drop → pickup → Inventory → Strength buff workflow is playable: PR #74 connects EnemyManagerComponent to deferred item creation, and PR #66 connects E to the separate itemPickup event and applies the bonus to baseAttack. Runtime logs recorded four pickups and one increase from 10 to 20. Removing a Charm is an API/test operation, not a player-facing UI action.
Current state
| Area | Current status | Evidence |
|---|---|---|
| Item data and type | Implemented | Charm and ItemType.STRENGTH_CHARM |
| Factory world item | Implemented | Positioned, renderable, independent, unregistered output |
| Physics layers | Implemented | ITEM uses bit 5; WEAPON uses bit 4 |
| Inventory storage | Implemented and tested | add/check/count/remove and Charm events; PR #66 |
| Pickup | Implemented and tested | ITEM overlap plus itemPickup; contact alone does not collect |
| Strength effect | Implemented and tested | First Charm adds 10 baseAttack; duplicates do not stack; final removal subtracts 10 |
| HUD | Implemented | Health, Strength (baseAttack), Charm count; PRs #46/#66 |
| EnemyManager world drops | Wired in production | Merged PR #74; EntityService deferred task creates/registers the drop |
| E-key input | Implemented | Emits interact for rooms and itemPickup for Charms; PR #66 |
| Standalone ItemDropOnDeathComponent | Isolated helper, not the production path | EnemyManagerComponent owns production drops |
| Pickup prompt / removal UI | Not implemented | Remain explicit limitations |
| Component integration regression | Tests passing; PR not yet merged | PR #88 at f2d45de |
Intended runtime flow
flowchart LR
A[Enemy entityDied] --> B[EntityService deferred task]
B --> C[ItemFactory.createDrop]
C --> D[Register ITEM entity]
D --> E[Player collisionStart]
E --> F[CharmPickupComponent tracks item]
F --> G[E key / itemPickup]
G --> H[InventoryComponent.addCharm]
H --> I[charmAdded]
I --> J[baseAttack +10 for first Charm]
J --> K[updateBaseAttack]
K --> L[HUD Strength refresh]
I --> M[HUD Charm count refresh]
Drop construction is deferred until after the entity update, avoiding creation during a locked physics step. The regression checks zero registrations before update, one after the first update, and still one after a second update. This tests one death event's queued execution, not arbitrary duplicate death-event handling.
Component responsibilities
| Class | Responsibility |
|---|---|
Charm |
Immutable Charm data containing its name |
ItemType |
Enumerates factory-supported item types; currently only STRENGTH_CHARM |
ItemComponent |
Associates a world entity with its Charm data |
ItemFactory |
Builds independent, renderable Strength Charm entities at requested positions |
EnemyManagerComponent |
Schedules production drops after enemy death, registers and owns dropped items for cleanup |
ItemDropOnDeathComponent |
Separate isolated helper; not attached as the production drop mechanism |
CharmPickupComponent |
Tracks nearby ITEM-layer entities and transfers one Charm on itemPickup |
InventoryComponent |
Stores Charms and emits charmAdded / charmRemoved |
CharmEffectComponent |
Applies and removes the non-stacking Strength bonus |
CombatStatsComponent |
Stores baseAttack and emits updateBaseAttack when it changes |
PlayerStatsDisplay |
Displays Health, Strength, and Strength Charm count |
PlayerFactory currently attaches InventoryComponent, CharmPickupComponent, CharmEffectComponent, CombatStatsComponent, and PlayerStatsDisplay to the player.
Item Factory contract
Use the parameterised factory method when a world position is known:
Entity item = ItemFactory.createDrop(ItemType.STRENGTH_CHARM, deathPosition);
ServiceLocator.getEntityService().register(item);
createDrop(...):
- rejects a null
ItemTypeor position; - creates a new, independent Charm and Entity on every call;
- assigns the requested world position;
- adds
TextureRenderComponent,PhysicsComponent, ITEM-layerHitboxComponent, andItemComponent; - returns the Entity without registering it.
The production caller is EnemyManagerComponent. It invokes this contract inside an EntityService deferred task, stores drops in droppedItems, and owns registration and lifecycle cleanup. createStrengthCharm() is the lower-level helper and returns an unpositioned, unregistered entity.
Pickup, Inventory and effect lifecycle
CharmPickupComponentrecords ITEM-layer entities duringcollisionStartand removes them duringcollisionEnd.- Collision alone does not collect an item.
- When
itemPickupfires, the first tracked item is read throughItemComponent. - Its Charm is added to
InventoryComponent, and the world entity is disposed. InventoryComponentemitscharmAdded.CharmEffectComponentadds 10 baseAttack when the first Strength Charm is added.PlayerStatsDisplayrefreshes both the Strength value and Charm count through events.- Removing Charms emits
charmRemoved; the bonus remains while at least one Strength Charm is held and is removed with the last one.
Event contract
| Event | Producer | Consumers |
|---|---|---|
entityDied |
CombatStatsComponent |
EnemyDeathComponent, tracked-enemy listeners in EnemyManagerComponent |
collisionStart / collisionEnd |
Physics contact handling | CharmPickupComponent |
interact |
E key | Room navigation |
itemPickup |
E key | CharmPickupComponent |
charmAdded |
InventoryComponent.addCharm |
CharmEffectComponent, PlayerStatsDisplay |
charmRemoved |
Successful InventoryComponent.removeCharm |
CharmEffectComponent, PlayerStatsDisplay |
updateBaseAttack |
CombatStatsComponent.setBaseAttack |
PlayerStatsDisplay |
Player values and HUD behaviour
The production configuration starts with Health 100 and baseAttack 10. The HUD label “Strength” displays baseAttack, not a separate stat. Initial expected values:
Health: 100
Strength: 10
Strength Charms: 0
After one pickup, assuming no combat health loss:
Health: 100
Strength: 20
Strength Charms: 1
Additional Strength Charms increase the count but not the +10 bonus. Removing the final Charm through InventoryComponent restores baseAttack to 10 in the automated test. Weapon damage calculation uses the increased baseAttack, subject to the weapon's multiplier.
Testing
Run from source with JDK 21:
./gradlew core:test --tests com.csse3200.game.components.rooms.EnemyManagerComponentTest
./gradlew core:test
./gradlew spotlessCheck
Results for the PR #88 verification candidate, items@f2d45de:
- EnemyManagerComponentTest: 5/5 passed.
- Full core:test: 355/355 passed.
- spotlessCheck: passed, recorded in the reviewed verification document. An earlier local formatter failure is historical and is not the current PR result.
- GitHub Unit Tests: success.
- GitHub Java Format: success.
- GitHub Javadoc Test Build: success.
- GitHub build / SonarCloud Check workflow: success.
- SonarCloud Code Analysis: success.
Coverage retained:
- ItemFactoryTest: independent positioned/renderable output, invalid requests, required components and ITEM layer.
- CharmPickupComponentTest: ITEM filtering, contact alone, room-interact isolation, itemPickup, leaving range.
- KeyboardPlayerInputComponentTest: E emits distinct room and pickup events.
- InventoryComponentTest: add/check/count/remove.
- CharmEffectComponentTest: +10 baseAttack, duplicates do not stack, final removal restores the bonus, increased damage.
- ItemFlowIntegrationTest: Factory → collision → itemPickup → Inventory → +10 baseAttack → removal/restoration.
- EnemyManagerComponentTest: component integration regression test, including deferred registration, death position, identity/layer, and no second-update duplication.
- Isolated ItemDropOnDeathComponentTest coverage does not imply production uses that helper.
PR #88 contains additional component integration regression coverage and is not yet merged.
Manual demonstration and evidence
- Launch the game under JDK 21.
- Start with Strength 10 and Charm count 0.
- Kill an enemy; show a Strength Charm at its death position.
- Touch the Charm and show contact alone does not pick it up.
- Press E: show the world item disappearing, Charm count 0 → 1 and Strength 10 → 20.
- Show increased weapon damage using the same weapon/comparable target, accounting for its multiplier.
- Pick up another Charm: the count increases without another Strength bonus.
- Cite the passing ItemFlowIntegrationTest as automated evidence that removing the final Charm restores baseAttack; do not imply that the game provides a removal button.
Earlier runtime logs recorded four successful pickups and one baseAttack change to 20.
Known limitations and next integration steps
- No pickup prompt.
- No player-facing Charm removal button; removal/restoration is verified through the inventory API and automated tests.
- Sprint 1 supports only deterministic Strength Charm drops; no second Charm type or randomised drop chance.
- PR #88 needs two reviewers from two different non-Team-5 groups. Do not self-merge.
- Do not replace the deferred EnemyManager path with the immediate standalone ItemDropOnDeathComponent during collision callbacks.
Related work
-
PR #38 — core Charm pickup; merged
-
PR #46 — Strength/Charm-count HUD; merged
-
PR #56 — physics-safe entity lifecycle; merged
-
PR #66 — items integration, baseAttack bonus and E/itemPickup contract; merged
-
PR #74 — EnemyManager deferred item drops; merged
-
PR #88 — component integration regression/evidence; OPEN, not yet merged
-
Issue #8 — Item/Charm System
-
Issue #15 — Factory and drop logic
-
Issue #23 — Charm inventory
-
Issue #24 — Item pickup
-
Issue #26 — Testing, documentation and final evidence
-
Issue #28 — Charm buff / combat stats
Sprint 1 change history
- Added deterministic Strength Charm creation and world rendering.
- Added ITEM-layer physics using bit 5, distinct from WEAPON on bit 4.
- Added collision tracking and interaction-gated pickup.
- Added Inventory storage and Charm add/remove events.
- Added a non-stacking +10 Strength effect with final-removal restoration.
- Added Health, Strength, and Strength Charm count to the HUD.
- Added unit and integration coverage for the component contracts and automated item flow.
- Integrated physics-safe EnemyManager drops and E/itemPickup; linked Strength to baseAttack.
- Added deferred/no-second-update-duplicate component integration coverage in PR #88.
- Updated the production integration, event contracts, test evidence and demonstration procedure.
AI assistance
OpenAI Codex assisted with consolidating repository evidence and drafting this documentation. Team 5 reviewed the technical claims against the merged code, tests, PR history, and runtime evidence.