Entity Helpers
github.com/mechanical-lich/ml-rogue-lib/pkg/rlentity
Stateless helper functions for common entity manipulations: movement, facing, interaction, and death detection. These are the low-level primitives used by AISystem, rlai, and game-specific input handlers.
For higher-level AI navigation (path following, range checks, target tracking) see rlai.
Functions
HandleDeath
func HandleDeath(entity *ecs.Entity) bool
Checks whether the entity should die and, if so, adds a DeadComponent and returns true.
Detection order:
- If the entity already has
DeadComponent, returnstrueimmediately. - If the entity has
BodyComponent, checks all parts for aKillsWhenBrokenorKillsWhenAmputatedcondition. - Regardless of whether a
BodyComponentis present, also checksHealthComponent.Health <= 0.
This means an entity can carry both components: lethal body-part damage kills it via step 2, and legacy health reduction kills it via step 3.
Call this at the start of an entity’s update to bail out early if it just died.
Move
func Move(entity *ecs.Entity, level rlworld.LevelInterface, deltaX, deltaY, deltaZ int) bool
Attempts to move the entity by (deltaX, deltaY, deltaZ). Returns true if a solid entity was blocking the destination.
Movement rules:
- If a
SolidComponententity is at the destination, movement is blocked — unless the blocker is aDoorComponentthe entity is allowed to pass through (checked viaCanPassThroughDoor). - If the destination tile is
IsAir, the entity instead drops to the solid tile directly below (simulates gravity). - Water tiles block movement entirely.
- Solid tiles block movement entirely.
HandleMovement
func HandleMovement(level rlworld.LevelInterface, entity *ecs.Entity, deltaX, deltaY, deltaZ int)
Combines Move and Face in one call. No-ops if all deltas are zero. Used internally by rlai.MoveTowardsTarget.
Face
func Face(entity *ecs.Entity, deltaX, deltaY int)
Updates the entity’s DirectionComponent based on a movement delta. Does nothing if the entity lacks a DirectionComponent.
deltaX / deltaY | Direction |
|---|---|
deltaX > 0 | 0 (right) |
deltaY > 0 | 1 (down) |
deltaY < 0 | 2 (up) |
deltaX < 0 | 3 (left) |
Eat
func Eat(entity, foodEntity *ecs.Entity) bool
Consumes one unit of food from foodEntity by decrementing FoodComponent.Amount. Returns true on success, false if foodEntity has no FoodComponent or if entity == foodEntity.
Swap
func Swap(level rlworld.LevelInterface, entity, entityHit *ecs.Entity)
Exchanges the grid positions of two entities. Calls level.PlaceEntity for both. Does nothing if they are the same entity.
CanPassThroughDoor
func CanPassThroughDoor(entity *ecs.Entity, door *rlcomponents.DoorComponent) bool
Returns true if the entity is allowed to move through the given door. An entity may pass if the door is open, or if the door’s OwnedBy faction matches the entity’s DescriptionComponent.Faction.
GetName
func GetName(entity *ecs.Entity) string
Returns the entity’s DescriptionComponent.Name, or "Unknown" if the component is absent.
FootprintBlockers
func FootprintBlockers(entity *ecs.Entity, level rlworld.LevelInterface, destX, destY, destZ int, buf *[]*ecs.Entity)
Appends all solid entities that overlap the entity’s footprint at (destX, destY, destZ) to buf, excluding entity itself. Useful for sized entities (those with SizeComponent) to enumerate every blocker before deciding whether to attack or open a door.
FindByID
func FindByID(level rlworld.LevelInterface, id string) *ecs.Entity
Searches all level entities for the first whose DescriptionComponent.ID matches id. Returns nil if not found or if id is empty.
FindByTag
func FindByTag(level rlworld.LevelInterface, tag string) []*ecs.Entity
Returns all entities whose DescriptionComponent.Tags slice contains tag.
CheckInteraction
func CheckInteraction(actor, target *ecs.Entity) bool
Fires all InteractionComponent triggers on target if it has one and has not yet been used (or is marked repeatable). Posts an InteractionEvent to MLGE’s queued event manager for each trigger, and optionally posts an interaction prompt message. Returns true if triggers fired.
CheckPassOver
func CheckPassOver(mover *ecs.Entity, level rlworld.LevelInterface, x, y, z int)
Call after a successful Move. For each entity at (x, y, z) other than the mover:
- Calls
CheckInteraction(walk-over pressure plates, floor triggers, etc.). - Posts a random
PassOverDescriptionmessage from the first entity that has one.
CheckExcuseMe
func CheckExcuseMe(bumped *ecs.Entity)
Posts a random ExcuseMeAnnouncement from bumped. Call this after a friendly position swap so the displaced entity can react.
CheckDeathAnnouncement
func CheckDeathAnnouncement(watcher *ecs.Entity, dying *ecs.Entity, level *rlworld.Level)
Posts a death message if watcher has line-of-sight to dying (same Z-level). Uses a random DeathAnnouncements string from the dying entity’s DescriptionComponent, or "<name> has died." as a fallback. Call this before the entity is removed from the level.
Usage Example
import (
"github.com/mechanical-lich/ml-rogue-lib/pkg/rlentity"
"github.com/mechanical-lich/mlge/utility"
)
// Player movement handler.
func movePlayer(player *ecs.Entity, level rlworld.LevelInterface, dx, dy int) {
if rlentity.HandleDeath(player) {
return
}
hitSolid := rlentity.Move(player, level, dx, dy, 0)
if hitSolid {
// bump-attack logic, open door prompt, etc.
}
rlentity.Face(player, dx, dy)
}
// Simple wander step.
func wanderStep(entity *ecs.Entity, level rlworld.LevelInterface) {
dx := utility.GetRandom(-1, 2)
dy := 0
if dx == 0 {
dy = utility.GetRandom(-1, 2)
}
rlentity.HandleMovement(level, entity, dx, dy, 0)
}