mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
Damaging NPCs
This commit is contained in:
parent
9fdf117c24
commit
6bcb59867e
12 changed files with 318 additions and 72 deletions
|
@ -2,6 +2,7 @@ package npc
|
|||
|
||||
import (
|
||||
"mvvasilev/last_light/engine"
|
||||
"mvvasilev/last_light/game/item"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/google/uuid"
|
||||
|
@ -49,8 +50,7 @@ func MovementDirectionOffset(dir Direction) (int, int) {
|
|||
|
||||
type Entity interface {
|
||||
UniqueId() uuid.UUID
|
||||
Input(e *tcell.EventKey)
|
||||
Tick(dt int64)
|
||||
Presentation() (rune, tcell.Style)
|
||||
}
|
||||
|
||||
type MovableEntity interface {
|
||||
|
@ -59,3 +59,9 @@ type MovableEntity interface {
|
|||
|
||||
Entity
|
||||
}
|
||||
|
||||
type EquippedEntity interface {
|
||||
Inventory() *item.EquippedInventory
|
||||
|
||||
Entity
|
||||
}
|
||||
|
|
|
@ -7,18 +7,34 @@ import (
|
|||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type NPC interface {
|
||||
Name() string
|
||||
|
||||
MovableEntity
|
||||
}
|
||||
|
||||
type BasicNPC struct {
|
||||
id uuid.UUID
|
||||
name string
|
||||
presentation rune
|
||||
style tcell.Style
|
||||
engine.Positioned
|
||||
}
|
||||
|
||||
func CreateNPC(pos engine.Position) *BasicNPC {
|
||||
func CreateNPC(pos engine.Position, name string, presentation rune, style tcell.Style) *BasicNPC {
|
||||
return &BasicNPC{
|
||||
id: uuid.New(),
|
||||
name: name,
|
||||
presentation: presentation,
|
||||
style: style,
|
||||
Positioned: engine.WithPosition(pos),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BasicNPC) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *BasicNPC) MoveTo(newPosition engine.Position) {
|
||||
c.Positioned.SetPosition(newPosition)
|
||||
}
|
||||
|
@ -27,8 +43,6 @@ func (c *BasicNPC) UniqueId() uuid.UUID {
|
|||
return c.id
|
||||
}
|
||||
|
||||
func (c *BasicNPC) Input(e *tcell.EventKey) {
|
||||
}
|
||||
|
||||
func (c *BasicNPC) Tick(dt int64) {
|
||||
func (c *BasicNPC) Presentation() (rune, tcell.Style) {
|
||||
return c.presentation, c.style
|
||||
}
|
||||
|
|
58
game/npc/rpg_npc.go
Normal file
58
game/npc/rpg_npc.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -25,10 +25,13 @@ func CreatePlayer(x, y int, playerStats map[rpg.Stat]int) *Player {
|
|||
p.position = engine.PositionAt(x, y)
|
||||
p.inventory = item.CreateEquippedInventory()
|
||||
p.BasicRPGEntity = rpg.CreateBasicRPGEntity(
|
||||
0,
|
||||
playerStats,
|
||||
map[rpg.Stat][]rpg.StatModifier{},
|
||||
)
|
||||
|
||||
p.Heal(rpg.BaseMaxHealth(p))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -48,21 +51,17 @@ func (p *Player) Presentation() (rune, tcell.Style) {
|
|||
return '@', tcell.StyleDefault
|
||||
}
|
||||
|
||||
func (p *Player) Passable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Player) Transparent() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Player) Inventory() *item.EquippedInventory {
|
||||
return p.inventory
|
||||
}
|
||||
|
||||
func (p *Player) Input(e *tcell.EventKey) {
|
||||
}
|
||||
|
||||
func (p *Player) Tick(dt int64) {
|
||||
func (p *Player) 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package rpg
|
||||
|
||||
import "slices"
|
||||
|
||||
type RPGEntity interface {
|
||||
BaseStat(stat Stat) int
|
||||
SetBaseStat(stat Stat, value int)
|
||||
|
@ -11,6 +13,8 @@ type RPGEntity interface {
|
|||
CurrentHealth() int
|
||||
Heal(health int)
|
||||
Damage(damage int)
|
||||
|
||||
CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType)
|
||||
}
|
||||
|
||||
type BasicRPGEntity struct {
|
||||
|
@ -21,11 +25,11 @@ type BasicRPGEntity struct {
|
|||
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{
|
||||
stats: baseStats,
|
||||
statModifiers: statModifiers,
|
||||
currentHealth: 0,
|
||||
currentHealth: health,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +64,13 @@ func (brpg *BasicRPGEntity) AddStatModifier(modifier StatModifier) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -88,3 +98,7 @@ func (brpg *BasicRPGEntity) Damage(damage int) {
|
|||
|
||||
brpg.currentHealth -= damage
|
||||
}
|
||||
|
||||
func (brpg *BasicRPGEntity) CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
||||
return UnarmedAttack(brpg, other)
|
||||
}
|
||||
|
|
|
@ -24,12 +24,6 @@ type RPGItemType interface {
|
|||
item.ItemType
|
||||
}
|
||||
|
||||
type RPGItem interface {
|
||||
Modifiers() []StatModifier
|
||||
|
||||
item.Item
|
||||
}
|
||||
|
||||
type BasicRPGItemType struct {
|
||||
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 {
|
||||
modifiers []StatModifier
|
||||
rpgType RPGItemType
|
||||
|
||||
item.BasicItem
|
||||
}
|
||||
|
@ -268,9 +270,14 @@ func (i *BasicRPGItem) Modifiers() []StatModifier {
|
|||
return i.modifiers
|
||||
}
|
||||
|
||||
func (i *BasicRPGItem) RPGType() RPGItemType {
|
||||
return i.rpgType
|
||||
}
|
||||
|
||||
func CreateRPGItem(name string, style tcell.Style, itemType RPGItemType, modifiers []StatModifier) RPGItem {
|
||||
return &BasicRPGItem{
|
||||
modifiers: modifiers,
|
||||
rpgType: itemType,
|
||||
BasicItem: item.CreateBasicItemWithName(
|
||||
name,
|
||||
style,
|
||||
|
|
|
@ -2,6 +2,7 @@ package rpg
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"mvvasilev/last_light/engine"
|
||||
)
|
||||
|
||||
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 StatModifier struct {
|
||||
|
@ -129,6 +160,33 @@ const (
|
|||
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 {
|
||||
switch dmgType {
|
||||
case DamageType_Physical_Unarmed:
|
||||
|
@ -142,7 +200,7 @@ func DamageTypeToBonusStat(dmgType DamageType) Stat {
|
|||
case DamageType_Magic_Fire:
|
||||
return Stat_DamageBonus_Magic_Fire
|
||||
case DamageType_Magic_Cold:
|
||||
return Stat_DamageBonus_Magic_Fire
|
||||
return Stat_DamageBonus_Magic_Cold
|
||||
case DamageType_Magic_Necrotic:
|
||||
return Stat_DamageBonus_Magic_Necrotic
|
||||
case DamageType_Magic_Thunder:
|
||||
|
@ -201,8 +259,12 @@ func MagicHitRoll(attacker RPGEntity, victim RPGEntity) bool {
|
|||
}
|
||||
|
||||
// true = hit lands, false = hit does not land
|
||||
func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) bool {
|
||||
return hitRoll(EvasionRoll(victim), PhysicalPrecisionRoll(attacker))
|
||||
func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) (hit bool, evasion, precision int) {
|
||||
evasion = EvasionRoll(victim)
|
||||
precision = PhysicalPrecisionRoll(attacker)
|
||||
hit = hitRoll(evasion, precision)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func hitRoll(evasionRoll, precisionRoll int) bool {
|
||||
|
@ -212,3 +274,38 @@ func hitRoll(evasionRoll, precisionRoll int) bool {
|
|||
func UnarmedDamage(attacker RPGEntity) int {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -58,21 +58,16 @@ func CreateCharacterCreationState(turnSystem *turns.TurnSystem, inputSystem *inp
|
|||
}
|
||||
|
||||
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 {
|
||||
if ccs.menuState.AvailablePoints == 0 {
|
||||
break
|
||||
}
|
||||
ccs.menuState.AvailablePoints = 0
|
||||
ccs.menuState.Stats = []*menu.StatState{}
|
||||
|
||||
limit := ccs.menuState.AvailablePoints
|
||||
|
||||
if limit > 20 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
s.Value = engine.RandInt(1, limit+1)
|
||||
ccs.menuState.AvailablePoints -= s.Value - 1
|
||||
for k, v := range stats {
|
||||
ccs.menuState.Stats = append(ccs.menuState.Stats, &menu.StatState{
|
||||
Stat: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
|
||||
ccs.ccMenu.UpdateState(ccs.menuState)
|
||||
|
@ -147,8 +142,16 @@ func (ccs *CharacterCreationState) OnTick(dt int64) GameState {
|
|||
case input.InputAction_Menu_HighlightLeft:
|
||||
ccs.DecreaseStatValue()
|
||||
case input.InputAction_Menu_HighlightDown:
|
||||
if ccs.menuState.CurrentHighlight > len(ccs.menuState.Stats) {
|
||||
break
|
||||
}
|
||||
|
||||
ccs.menuState.CurrentHighlight++
|
||||
case input.InputAction_Menu_HighlightUp:
|
||||
if ccs.menuState.CurrentHighlight == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
ccs.menuState.CurrentHighlight--
|
||||
case input.InputAction_Menu_Select:
|
||||
ccs.ccMenu.SelectHighlight()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mvvasilev/last_light/engine"
|
||||
"mvvasilev/last_light/game/input"
|
||||
"mvvasilev/last_light/game/npc"
|
||||
|
@ -19,7 +20,7 @@ type PlayingState struct {
|
|||
inputSystem *input.InputSystem
|
||||
|
||||
player *player.Player
|
||||
someNPC *npc.BasicNPC
|
||||
someNPC npc.RPGNPC
|
||||
|
||||
eventLog *engine.GameEventLog
|
||||
uiEventLog *ui.UIEventLog
|
||||
|
@ -52,7 +53,6 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
|||
s.dungeon.CurrentLevel().PlayerSpawnPoint().Y(),
|
||||
playerStats,
|
||||
)
|
||||
s.player.Heal(rpg.BaseMaxHealth(s.player))
|
||||
|
||||
s.turnSystem.Schedule(10, func() (complete bool, requeue bool) {
|
||||
requeue = true
|
||||
|
@ -89,7 +89,14 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
|||
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.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.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.someNPC, 'N', tcell.StyleDefault)
|
||||
s.dungeon.CurrentLevel().AddEntity(s.player)
|
||||
s.dungeon.CurrentLevel().AddEntity(s.someNPC)
|
||||
|
||||
s.viewport = engine.CreateViewport(
|
||||
engine.PositionAt(0, 0),
|
||||
|
@ -132,9 +139,27 @@ func (ps *PlayingState) MovePlayer(direction npc.Direction) {
|
|||
dx, dy := npc.MovementDirectionOffset(direction)
|
||||
ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy)
|
||||
ps.viewport.SetCenter(ps.player.Position())
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -184,7 +209,7 @@ func (ps *PlayingState) SwitchToNextLevel() {
|
|||
tcell.StyleDefault,
|
||||
)
|
||||
|
||||
ps.dungeon.CurrentLevel().AddEntity(ps.player, '@', tcell.StyleDefault)
|
||||
ps.dungeon.CurrentLevel().AddEntity(ps.player)
|
||||
}
|
||||
|
||||
func (ps *PlayingState) SwitchToPreviousLevel() {
|
||||
|
@ -220,7 +245,7 @@ func (ps *PlayingState) SwitchToPreviousLevel() {
|
|||
tcell.StyleDefault,
|
||||
)
|
||||
|
||||
ps.dungeon.CurrentLevel().AddEntity(ps.player, '@', tcell.StyleDefault)
|
||||
ps.dungeon.CurrentLevel().AddEntity(ps.player)
|
||||
}
|
||||
|
||||
func (ps *PlayingState) PickUpItemUnderPlayer() {
|
||||
|
@ -255,6 +280,10 @@ func (ps *PlayingState) HasLineOfSight(start, end engine.Position) bool {
|
|||
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() {
|
||||
playerVisibleAndInRange := false
|
||||
|
||||
|
@ -291,6 +320,21 @@ func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
|||
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(
|
||||
ps.someNPC.Position(),
|
||||
ps.player.Position(),
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"mvvasilev/last_light/game/rpg"
|
||||
"slices"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
@ -240,8 +239,8 @@ func (d *DungeonLevel) DropEntity(uuid uuid.UUID) {
|
|||
d.entityLevel.DropEntity(uuid)
|
||||
}
|
||||
|
||||
func (d *DungeonLevel) AddEntity(entity npc.MovableEntity, presentation rune, style tcell.Style) {
|
||||
d.entityLevel.AddEntity(entity, presentation, style)
|
||||
func (d *DungeonLevel) AddEntity(entity npc.MovableEntity) {
|
||||
d.entityLevel.AddEntity(entity)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func (d *DungeonLevel) EntityAt(x, y int) (e npc.MovableEntity) {
|
||||
return d.entityLevel.EntityAt(x, y)
|
||||
}
|
||||
|
||||
func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool {
|
||||
if !d.groundLevel.Size().Contains(x, y) {
|
||||
return false
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"mvvasilev/last_light/engine"
|
||||
"mvvasilev/last_light/game/npc"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
@ -43,13 +42,13 @@ func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTil
|
|||
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()) {
|
||||
return
|
||||
}
|
||||
|
||||
key := em.Size().AsArrayIndex(entity.Position().XY())
|
||||
et := CreateBasicEntityTile(entity, presentation, style)
|
||||
et := CreateBasicEntityTile(entity)
|
||||
|
||||
em.entities[key] = et
|
||||
}
|
||||
|
@ -111,6 +110,16 @@ func (em *EntityMap) TileAt(x int, y int) Tile {
|
|||
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 {
|
||||
return em.FitsWithin(x, y)
|
||||
}
|
||||
|
@ -124,7 +133,4 @@ func (em *EntityMap) ExploredTileAt(x, y int) Tile {
|
|||
}
|
||||
|
||||
func (em *EntityMap) Tick(dt int64) {
|
||||
for _, e := range em.entities {
|
||||
e.Entity().Tick(dt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,16 +237,11 @@ type EntityTile interface {
|
|||
|
||||
type BasicEntityTile struct {
|
||||
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{
|
||||
entity: entity,
|
||||
presentation: presentation,
|
||||
style: style,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,7 +254,7 @@ func (bet *BasicEntityTile) Position() engine.Position {
|
|||
}
|
||||
|
||||
func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) {
|
||||
return bet.presentation, bet.style
|
||||
return bet.entity.Presentation()
|
||||
}
|
||||
|
||||
func (bet *BasicEntityTile) Passable() bool {
|
||||
|
|
Loading…
Add table
Reference in a new issue