Damaging NPCs

This commit is contained in:
Miroslav Vasilev 2024-06-01 11:20:51 +03:00
parent 9fdf117c24
commit 6bcb59867e
12 changed files with 318 additions and 72 deletions

View file

@ -2,6 +2,7 @@ package npc
import ( import (
"mvvasilev/last_light/engine" "mvvasilev/last_light/engine"
"mvvasilev/last_light/game/item"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/google/uuid" "github.com/google/uuid"
@ -49,8 +50,7 @@ func MovementDirectionOffset(dir Direction) (int, int) {
type Entity interface { type Entity interface {
UniqueId() uuid.UUID UniqueId() uuid.UUID
Input(e *tcell.EventKey) Presentation() (rune, tcell.Style)
Tick(dt int64)
} }
type MovableEntity interface { type MovableEntity interface {
@ -59,3 +59,9 @@ type MovableEntity interface {
Entity Entity
} }
type EquippedEntity interface {
Inventory() *item.EquippedInventory
Entity
}

View file

@ -7,18 +7,34 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
type NPC interface {
Name() string
MovableEntity
}
type BasicNPC struct { type BasicNPC struct {
id uuid.UUID id uuid.UUID
name string
presentation rune
style tcell.Style
engine.Positioned engine.Positioned
} }
func CreateNPC(pos engine.Position) *BasicNPC { func CreateNPC(pos engine.Position, name string, presentation rune, style tcell.Style) *BasicNPC {
return &BasicNPC{ return &BasicNPC{
id: uuid.New(), id: uuid.New(),
name: name,
presentation: presentation,
style: style,
Positioned: engine.WithPosition(pos), Positioned: engine.WithPosition(pos),
} }
} }
func (c *BasicNPC) Name() string {
return c.name
}
func (c *BasicNPC) MoveTo(newPosition engine.Position) { func (c *BasicNPC) MoveTo(newPosition engine.Position) {
c.Positioned.SetPosition(newPosition) c.Positioned.SetPosition(newPosition)
} }
@ -27,8 +43,6 @@ func (c *BasicNPC) UniqueId() uuid.UUID {
return c.id return c.id
} }
func (c *BasicNPC) Input(e *tcell.EventKey) { func (c *BasicNPC) Presentation() (rune, tcell.Style) {
} return c.presentation, c.style
func (c *BasicNPC) Tick(dt int64) {
} }

58
game/npc/rpg_npc.go Normal file
View file

@ -0,0 +1,58 @@
package npc
import (
"mvvasilev/last_light/engine"
"mvvasilev/last_light/game/item"
"mvvasilev/last_light/game/rpg"
"github.com/gdamore/tcell/v2"
)
type RPGNPC interface {
NPC
rpg.RPGEntity
EquippedEntity
}
type BasicRPGNPC struct {
inventory *item.EquippedInventory
*BasicNPC
*rpg.BasicRPGEntity
}
func CreateRPGNPC(x, y int, name string, representation rune, style tcell.Style, stats map[rpg.Stat]int) *BasicRPGNPC {
rpgnpc := &BasicRPGNPC{
inventory: item.CreateEquippedInventory(),
BasicNPC: CreateNPC(
engine.PositionAt(x, y),
name,
representation,
style,
),
BasicRPGEntity: rpg.CreateBasicRPGEntity(
0,
stats,
map[rpg.Stat][]rpg.StatModifier{},
),
}
rpgnpc.Heal(rpg.BaseMaxHealth(rpgnpc))
return rpgnpc
}
func (rnpc *BasicRPGNPC) Inventory() *item.EquippedInventory {
return rnpc.inventory
}
func (p *BasicRPGNPC) CalculateAttack(other rpg.RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType rpg.DamageType) {
mainHand := p.inventory.AtSlot(item.EquippedSlotDominantHand)
switch mh := mainHand.(type) {
case rpg.RPGItem:
return rpg.PhysicalWeaponAttack(p, mh, other)
default:
return rpg.UnarmedAttack(p, other)
}
}

View file

@ -25,10 +25,13 @@ func CreatePlayer(x, y int, playerStats map[rpg.Stat]int) *Player {
p.position = engine.PositionAt(x, y) p.position = engine.PositionAt(x, y)
p.inventory = item.CreateEquippedInventory() p.inventory = item.CreateEquippedInventory()
p.BasicRPGEntity = rpg.CreateBasicRPGEntity( p.BasicRPGEntity = rpg.CreateBasicRPGEntity(
0,
playerStats, playerStats,
map[rpg.Stat][]rpg.StatModifier{}, map[rpg.Stat][]rpg.StatModifier{},
) )
p.Heal(rpg.BaseMaxHealth(p))
return p return p
} }
@ -48,21 +51,17 @@ func (p *Player) Presentation() (rune, tcell.Style) {
return '@', tcell.StyleDefault return '@', tcell.StyleDefault
} }
func (p *Player) Passable() bool {
return false
}
func (p *Player) Transparent() bool {
return false
}
func (p *Player) Inventory() *item.EquippedInventory { func (p *Player) Inventory() *item.EquippedInventory {
return p.inventory return p.inventory
} }
func (p *Player) Input(e *tcell.EventKey) { func (p *Player) CalculateAttack(other rpg.RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType rpg.DamageType) {
} mainHand := p.inventory.AtSlot(item.EquippedSlotDominantHand)
func (p *Player) Tick(dt int64) {
switch mh := mainHand.(type) {
case rpg.RPGItem:
return rpg.PhysicalWeaponAttack(p, mh, other)
default:
return rpg.UnarmedAttack(p, other)
}
} }

View file

@ -1,5 +1,7 @@
package rpg package rpg
import "slices"
type RPGEntity interface { type RPGEntity interface {
BaseStat(stat Stat) int BaseStat(stat Stat) int
SetBaseStat(stat Stat, value int) SetBaseStat(stat Stat, value int)
@ -11,6 +13,8 @@ type RPGEntity interface {
CurrentHealth() int CurrentHealth() int
Heal(health int) Heal(health int)
Damage(damage int) Damage(damage int)
CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType)
} }
type BasicRPGEntity struct { type BasicRPGEntity struct {
@ -21,11 +25,11 @@ type BasicRPGEntity struct {
currentHealth int currentHealth int
} }
func CreateBasicRPGEntity(baseStats map[Stat]int, statModifiers map[Stat][]StatModifier) *BasicRPGEntity { func CreateBasicRPGEntity(health int, baseStats map[Stat]int, statModifiers map[Stat][]StatModifier) *BasicRPGEntity {
return &BasicRPGEntity{ return &BasicRPGEntity{
stats: baseStats, stats: baseStats,
statModifiers: statModifiers, statModifiers: statModifiers,
currentHealth: 0, currentHealth: health,
} }
} }
@ -60,7 +64,13 @@ func (brpg *BasicRPGEntity) AddStatModifier(modifier StatModifier) {
} }
func (brpg *BasicRPGEntity) RemoveStatModifier(id StatModifierId) { func (brpg *BasicRPGEntity) RemoveStatModifier(id StatModifierId) {
// TODO for k, v := range brpg.statModifiers {
for i, sm := range v {
if sm.Id == id {
brpg.statModifiers[k] = slices.Delete(v, i, i+1)
}
}
}
} }
func (brpg *BasicRPGEntity) CurrentHealth() int { func (brpg *BasicRPGEntity) CurrentHealth() int {
@ -88,3 +98,7 @@ func (brpg *BasicRPGEntity) Damage(damage int) {
brpg.currentHealth -= damage brpg.currentHealth -= damage
} }
func (brpg *BasicRPGEntity) CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
return UnarmedAttack(brpg, other)
}

View file

@ -24,12 +24,6 @@ type RPGItemType interface {
item.ItemType item.ItemType
} }
type RPGItem interface {
Modifiers() []StatModifier
item.Item
}
type BasicRPGItemType struct { type BasicRPGItemType struct {
damageRollFunc func(victim, attacker RPGEntity) (damage int, dmgType DamageType) damageRollFunc func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
@ -258,8 +252,16 @@ func ItemTypeSpear() RPGItemType {
} }
} }
type RPGItem interface {
Modifiers() []StatModifier
RPGType() RPGItemType
item.Item
}
type BasicRPGItem struct { type BasicRPGItem struct {
modifiers []StatModifier modifiers []StatModifier
rpgType RPGItemType
item.BasicItem item.BasicItem
} }
@ -268,9 +270,14 @@ func (i *BasicRPGItem) Modifiers() []StatModifier {
return i.modifiers return i.modifiers
} }
func (i *BasicRPGItem) RPGType() RPGItemType {
return i.rpgType
}
func CreateRPGItem(name string, style tcell.Style, itemType RPGItemType, modifiers []StatModifier) RPGItem { func CreateRPGItem(name string, style tcell.Style, itemType RPGItemType, modifiers []StatModifier) RPGItem {
return &BasicRPGItem{ return &BasicRPGItem{
modifiers: modifiers, modifiers: modifiers,
rpgType: itemType,
BasicItem: item.CreateBasicItemWithName( BasicItem: item.CreateBasicItemWithName(
name, name,
style, style,

View file

@ -2,6 +2,7 @@ package rpg
import ( import (
"math/rand" "math/rand"
"mvvasilev/last_light/engine"
) )
type Stat int type Stat int
@ -51,6 +52,36 @@ func StatLongName(stat Stat) string {
} }
} }
func RandomStats(pointsAvailable int, min, max int, stats []Stat) (result map[Stat]int) {
result = map[Stat]int{}
for {
if pointsAvailable == 0 {
break
}
for _, s := range stats {
if pointsAvailable == 0 {
break
}
limit := pointsAvailable
if limit > max {
limit = max
}
value := engine.RandInt(min+1, limit+min)
result[s] += value
pointsAvailable -= value - min
}
}
return
}
type StatModifierId string type StatModifierId string
type StatModifier struct { type StatModifier struct {
@ -129,6 +160,33 @@ const (
DamageType_Magic_Poison DamageType = 9 DamageType_Magic_Poison DamageType = 9
) )
func DamageTypeName(dmgType DamageType) string {
switch dmgType {
case DamageType_Physical_Unarmed:
return "Unarmed"
case DamageType_Physical_Slashing:
return "Slashing"
case DamageType_Physical_Piercing:
return "Piercing"
case DamageType_Physical_Bludgeoning:
return "Bludgeoning"
case DamageType_Magic_Fire:
return "Fire"
case DamageType_Magic_Cold:
return "Cold"
case DamageType_Magic_Necrotic:
return "Necrotic"
case DamageType_Magic_Thunder:
return "Thunder"
case DamageType_Magic_Acid:
return "Acid"
case DamageType_Magic_Poison:
return "Poison"
default:
return "Unknown"
}
}
func DamageTypeToBonusStat(dmgType DamageType) Stat { func DamageTypeToBonusStat(dmgType DamageType) Stat {
switch dmgType { switch dmgType {
case DamageType_Physical_Unarmed: case DamageType_Physical_Unarmed:
@ -142,7 +200,7 @@ func DamageTypeToBonusStat(dmgType DamageType) Stat {
case DamageType_Magic_Fire: case DamageType_Magic_Fire:
return Stat_DamageBonus_Magic_Fire return Stat_DamageBonus_Magic_Fire
case DamageType_Magic_Cold: case DamageType_Magic_Cold:
return Stat_DamageBonus_Magic_Fire return Stat_DamageBonus_Magic_Cold
case DamageType_Magic_Necrotic: case DamageType_Magic_Necrotic:
return Stat_DamageBonus_Magic_Necrotic return Stat_DamageBonus_Magic_Necrotic
case DamageType_Magic_Thunder: case DamageType_Magic_Thunder:
@ -201,8 +259,12 @@ func MagicHitRoll(attacker RPGEntity, victim RPGEntity) bool {
} }
// true = hit lands, false = hit does not land // true = hit lands, false = hit does not land
func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) bool { func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) (hit bool, evasion, precision int) {
return hitRoll(EvasionRoll(victim), PhysicalPrecisionRoll(attacker)) evasion = EvasionRoll(victim)
precision = PhysicalPrecisionRoll(attacker)
hit = hitRoll(evasion, precision)
return
} }
func hitRoll(evasionRoll, precisionRoll int) bool { func hitRoll(evasionRoll, precisionRoll int) bool {
@ -212,3 +274,38 @@ func hitRoll(evasionRoll, precisionRoll int) bool {
func UnarmedDamage(attacker RPGEntity) int { func UnarmedDamage(attacker RPGEntity) int {
return RollD4(1) + StatValue(attacker, Stat_DamageBonus_Physical_Unarmed) return RollD4(1) + StatValue(attacker, Stat_DamageBonus_Physical_Unarmed)
} }
func PhysicalWeaponDamange(attacker RPGEntity, weapon RPGItem, victim RPGEntity) (totalDamage int, dmgType DamageType) {
totalDamage, dmgType = weapon.RPGType().RollDamage()(victim, attacker)
bonusDmgStat := DamageTypeToBonusStat(dmgType)
totalDamage = totalDamage + StatValue(attacker, bonusDmgStat)
return
}
func UnarmedAttack(attacker RPGEntity, victim RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
hit, evasionRoll, precisionRoll = PhysicalHitRoll(attacker, victim)
if !hit {
return
}
damage = UnarmedDamage(attacker)
damageType = DamageType_Physical_Unarmed
return
}
func PhysicalWeaponAttack(attacker RPGEntity, weapon RPGItem, victim RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
hit, evasionRoll, precisionRoll = PhysicalHitRoll(attacker, victim)
if !hit {
return
}
damage, damageType = PhysicalWeaponDamange(attacker, weapon, victim)
return
}

View file

@ -58,21 +58,16 @@ func CreateCharacterCreationState(turnSystem *turns.TurnSystem, inputSystem *inp
} }
ccs.menuState.RandomizeCharacter = func() { ccs.menuState.RandomizeCharacter = func() {
ccs.menuState.AvailablePoints = 21 stats := rpg.RandomStats(21, 1, 20, []rpg.Stat{rpg.Stat_Attributes_Strength, rpg.Stat_Attributes_Constitution, rpg.Stat_Attributes_Intelligence, rpg.Stat_Attributes_Dexterity})
for _, s := range ccs.menuState.Stats { ccs.menuState.AvailablePoints = 0
if ccs.menuState.AvailablePoints == 0 { ccs.menuState.Stats = []*menu.StatState{}
break
}
limit := ccs.menuState.AvailablePoints for k, v := range stats {
ccs.menuState.Stats = append(ccs.menuState.Stats, &menu.StatState{
if limit > 20 { Stat: k,
limit = 20 Value: v,
} })
s.Value = engine.RandInt(1, limit+1)
ccs.menuState.AvailablePoints -= s.Value - 1
} }
ccs.ccMenu.UpdateState(ccs.menuState) ccs.ccMenu.UpdateState(ccs.menuState)
@ -147,8 +142,16 @@ func (ccs *CharacterCreationState) OnTick(dt int64) GameState {
case input.InputAction_Menu_HighlightLeft: case input.InputAction_Menu_HighlightLeft:
ccs.DecreaseStatValue() ccs.DecreaseStatValue()
case input.InputAction_Menu_HighlightDown: case input.InputAction_Menu_HighlightDown:
if ccs.menuState.CurrentHighlight > len(ccs.menuState.Stats) {
break
}
ccs.menuState.CurrentHighlight++ ccs.menuState.CurrentHighlight++
case input.InputAction_Menu_HighlightUp: case input.InputAction_Menu_HighlightUp:
if ccs.menuState.CurrentHighlight == 0 {
break
}
ccs.menuState.CurrentHighlight-- ccs.menuState.CurrentHighlight--
case input.InputAction_Menu_Select: case input.InputAction_Menu_Select:
ccs.ccMenu.SelectHighlight() ccs.ccMenu.SelectHighlight()

View file

@ -1,6 +1,7 @@
package state package state
import ( import (
"fmt"
"mvvasilev/last_light/engine" "mvvasilev/last_light/engine"
"mvvasilev/last_light/game/input" "mvvasilev/last_light/game/input"
"mvvasilev/last_light/game/npc" "mvvasilev/last_light/game/npc"
@ -19,7 +20,7 @@ type PlayingState struct {
inputSystem *input.InputSystem inputSystem *input.InputSystem
player *player.Player player *player.Player
someNPC *npc.BasicNPC someNPC npc.RPGNPC
eventLog *engine.GameEventLog eventLog *engine.GameEventLog
uiEventLog *ui.UIEventLog uiEventLog *ui.UIEventLog
@ -52,7 +53,6 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
s.dungeon.CurrentLevel().PlayerSpawnPoint().Y(), s.dungeon.CurrentLevel().PlayerSpawnPoint().Y(),
playerStats, playerStats,
) )
s.player.Heal(rpg.BaseMaxHealth(s.player))
s.turnSystem.Schedule(10, func() (complete bool, requeue bool) { s.turnSystem.Schedule(10, func() (complete bool, requeue bool) {
requeue = true requeue = true
@ -89,7 +89,14 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
return return
}) })
s.someNPC = npc.CreateNPC(s.dungeon.CurrentLevel().NextLevelStaircase()) s.someNPC = npc.CreateRPGNPC(
s.dungeon.CurrentLevel().NextLevelStaircase().X(),
s.dungeon.CurrentLevel().NextLevelStaircase().Y(),
"NPC",
'n',
tcell.StyleDefault,
rpg.RandomStats(21, 1, 20, []rpg.Stat{rpg.Stat_Attributes_Strength, rpg.Stat_Attributes_Constitution, rpg.Stat_Attributes_Intelligence, rpg.Stat_Attributes_Dexterity}),
)
s.turnSystem.Schedule(20, func() (complete bool, requeue bool) { s.turnSystem.Schedule(20, func() (complete bool, requeue bool) {
s.CalcPathToPlayerAndMove() s.CalcPathToPlayerAndMove()
@ -102,8 +109,8 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
s.uiEventLog = ui.CreateUIEventLog(0, 17, 80, 7, s.eventLog, tcell.StyleDefault) s.uiEventLog = ui.CreateUIEventLog(0, 17, 80, 7, s.eventLog, tcell.StyleDefault)
s.healthBar = ui.CreateHealthBar(68, 0, 12, 3, s.player.CurrentHealth(), rpg.BaseMaxHealth(s.player), tcell.StyleDefault) s.healthBar = ui.CreateHealthBar(68, 0, 12, 3, s.player.CurrentHealth(), rpg.BaseMaxHealth(s.player), tcell.StyleDefault)
s.dungeon.CurrentLevel().AddEntity(s.player, '@', tcell.StyleDefault) s.dungeon.CurrentLevel().AddEntity(s.player)
s.dungeon.CurrentLevel().AddEntity(s.someNPC, 'N', tcell.StyleDefault) s.dungeon.CurrentLevel().AddEntity(s.someNPC)
s.viewport = engine.CreateViewport( s.viewport = engine.CreateViewport(
engine.PositionAt(0, 0), engine.PositionAt(0, 0),
@ -132,11 +139,29 @@ func (ps *PlayingState) MovePlayer(direction npc.Direction) {
dx, dy := npc.MovementDirectionOffset(direction) dx, dy := npc.MovementDirectionOffset(direction)
ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy) ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy)
ps.viewport.SetCenter(ps.player.Position()) ps.viewport.SetCenter(ps.player.Position())
}
ps.eventLog.Log("You moved " + npc.DirectionName(direction)) ps.eventLog.Log("You moved " + npc.DirectionName(direction))
} }
ent := ps.dungeon.CurrentLevel().EntityAt(newPlayerPos.XY())
// We are moving into an entity. Attack it.
if ent != nil {
switch rpge := ent.(type) {
case npc.RPGNPC:
hit, precision, evasion, dmg, dmgType := ps.player.CalculateAttack(rpge)
if !hit {
ps.eventLog.Log(fmt.Sprintf("You attacked %v, but missed ( %v Evasion vs %v Precision)", rpge.Name(), evasion, precision))
return
}
rpge.Damage(dmg)
ps.eventLog.Log(fmt.Sprintf("You attacked %v, and hit for %v %v damage", rpge.Name(), dmg, rpg.DamageTypeName(dmgType)))
}
}
}
func (ps *PlayingState) InteractBelowPlayer() { func (ps *PlayingState) InteractBelowPlayer() {
playerPos := ps.player.Position() playerPos := ps.player.Position()
@ -184,7 +209,7 @@ func (ps *PlayingState) SwitchToNextLevel() {
tcell.StyleDefault, tcell.StyleDefault,
) )
ps.dungeon.CurrentLevel().AddEntity(ps.player, '@', tcell.StyleDefault) ps.dungeon.CurrentLevel().AddEntity(ps.player)
} }
func (ps *PlayingState) SwitchToPreviousLevel() { func (ps *PlayingState) SwitchToPreviousLevel() {
@ -220,7 +245,7 @@ func (ps *PlayingState) SwitchToPreviousLevel() {
tcell.StyleDefault, tcell.StyleDefault,
) )
ps.dungeon.CurrentLevel().AddEntity(ps.player, '@', tcell.StyleDefault) ps.dungeon.CurrentLevel().AddEntity(ps.player)
} }
func (ps *PlayingState) PickUpItemUnderPlayer() { func (ps *PlayingState) PickUpItemUnderPlayer() {
@ -255,6 +280,10 @@ func (ps *PlayingState) HasLineOfSight(start, end engine.Position) bool {
return true return true
} }
func (ps *PlayingState) PlayerWithinHitRange(pos engine.Position) bool {
return pos.WithOffset(-1, 0) == ps.player.Position() || pos.WithOffset(+1, 0) == ps.player.Position() || pos.WithOffset(0, -1) == ps.player.Position() || pos.WithOffset(0, +1) == ps.player.Position()
}
func (ps *PlayingState) CalcPathToPlayerAndMove() { func (ps *PlayingState) CalcPathToPlayerAndMove() {
playerVisibleAndInRange := false playerVisibleAndInRange := false
@ -291,6 +320,21 @@ func (ps *PlayingState) CalcPathToPlayerAndMove() {
return return
} }
if ps.PlayerWithinHitRange(ps.someNPC.Position()) {
hit, precision, evasion, dmg, dmgType := ps.player.CalculateAttack(ps.player)
if !hit {
ps.eventLog.Log(fmt.Sprintf("%v attacked you, but missed ( %v Evasion vs %v Precision)", ps.someNPC.Name(), evasion, precision))
return
}
ps.player.Damage(dmg)
ps.healthBar.SetHealth(ps.player.CurrentHealth())
ps.eventLog.Log(fmt.Sprintf("%v attacked you, and hit for %v %v damage", ps.someNPC.Name(), dmg, rpg.DamageTypeName(dmgType)))
return
}
pathToPlayer := engine.FindPath( pathToPlayer := engine.FindPath(
ps.someNPC.Position(), ps.someNPC.Position(),
ps.player.Position(), ps.player.Position(),

View file

@ -8,7 +8,6 @@ import (
"mvvasilev/last_light/game/rpg" "mvvasilev/last_light/game/rpg"
"slices" "slices"
"github.com/gdamore/tcell/v2"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -240,8 +239,8 @@ func (d *DungeonLevel) DropEntity(uuid uuid.UUID) {
d.entityLevel.DropEntity(uuid) d.entityLevel.DropEntity(uuid)
} }
func (d *DungeonLevel) AddEntity(entity npc.MovableEntity, presentation rune, style tcell.Style) { func (d *DungeonLevel) AddEntity(entity npc.MovableEntity) {
d.entityLevel.AddEntity(entity, presentation, style) d.entityLevel.AddEntity(entity)
} }
func (d *DungeonLevel) MoveEntity(uuid uuid.UUID, dx, dy int) { func (d *DungeonLevel) MoveEntity(uuid uuid.UUID, dx, dy int) {
@ -291,6 +290,10 @@ func (d *DungeonLevel) IsTilePassable(x, y int) bool {
return d.TileAt(x, y).Passable() return d.TileAt(x, y).Passable()
} }
func (d *DungeonLevel) EntityAt(x, y int) (e npc.MovableEntity) {
return d.entityLevel.EntityAt(x, y)
}
func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool { func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool {
if !d.groundLevel.Size().Contains(x, y) { if !d.groundLevel.Size().Contains(x, y) {
return false return false

View file

@ -5,7 +5,6 @@ import (
"mvvasilev/last_light/engine" "mvvasilev/last_light/engine"
"mvvasilev/last_light/game/npc" "mvvasilev/last_light/game/npc"
"github.com/gdamore/tcell/v2"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -43,13 +42,13 @@ func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTil
return -1, nil return -1, nil
} }
func (em *EntityMap) AddEntity(entity npc.MovableEntity, presentation rune, style tcell.Style) { func (em *EntityMap) AddEntity(entity npc.MovableEntity) {
if !em.FitsWithin(entity.Position().XY()) { if !em.FitsWithin(entity.Position().XY()) {
return return
} }
key := em.Size().AsArrayIndex(entity.Position().XY()) key := em.Size().AsArrayIndex(entity.Position().XY())
et := CreateBasicEntityTile(entity, presentation, style) et := CreateBasicEntityTile(entity)
em.entities[key] = et em.entities[key] = et
} }
@ -111,6 +110,16 @@ func (em *EntityMap) TileAt(x int, y int) Tile {
return em.entities[key] return em.entities[key]
} }
func (em *EntityMap) EntityAt(x, y int) (ent npc.MovableEntity) {
tile := em.TileAt(x, y)
if tile == nil {
return nil
}
return tile.(EntityTile).Entity()
}
func (em *EntityMap) IsInBounds(x, y int) bool { func (em *EntityMap) IsInBounds(x, y int) bool {
return em.FitsWithin(x, y) return em.FitsWithin(x, y)
} }
@ -124,7 +133,4 @@ func (em *EntityMap) ExploredTileAt(x, y int) Tile {
} }
func (em *EntityMap) Tick(dt int64) { func (em *EntityMap) Tick(dt int64) {
for _, e := range em.entities {
e.Entity().Tick(dt)
}
} }

View file

@ -237,16 +237,11 @@ type EntityTile interface {
type BasicEntityTile struct { type BasicEntityTile struct {
entity npc.MovableEntity entity npc.MovableEntity
presentation rune
style tcell.Style
} }
func CreateBasicEntityTile(entity npc.MovableEntity, presentation rune, style tcell.Style) *BasicEntityTile { func CreateBasicEntityTile(entity npc.MovableEntity) *BasicEntityTile {
return &BasicEntityTile{ return &BasicEntityTile{
entity: entity, entity: entity,
presentation: presentation,
style: style,
} }
} }
@ -259,7 +254,7 @@ func (bet *BasicEntityTile) Position() engine.Position {
} }
func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) { func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) {
return bet.presentation, bet.style return bet.entity.Presentation()
} }
func (bet *BasicEntityTile) Passable() bool { func (bet *BasicEntityTile) Passable() bool {