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 (
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
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.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue