Simulated arrows. Why...

This commit is contained in:
Miroslav Vasilev 2024-06-10 23:20:38 +03:00
parent e8f3c6ca9e
commit 855fa8dfc1
12 changed files with 246 additions and 85 deletions

View file

@ -38,3 +38,20 @@ func (p *Path) Next() (current Position, hasNext bool) {
return p.CurrentPosition(), true return p.CurrentPosition(), true
} }
func LinePath(from, to Position) *Path {
points := make([]Position, 0)
n := float64(from.Distance(to))
for step := 0.0; step <= n; step += 1.0 {
t := 0.0
if n != 0 {
t = step / n
}
points = append(points, LerpPositions(from, to, t))
}
return CreatePath(from, to, points)
}

View file

@ -35,7 +35,7 @@ func FindPath(from Position, to Position, maxDistance int, isPassable func(x, y
iteration++ iteration++
if iteration >= maxDistance { if iteration >= maxDistance {
return nil break
} }
if len(openList) == 0 { if len(openList) == 0 {

View file

@ -173,3 +173,14 @@ func AbsInt(val int) int {
} }
return val return val
} }
func Lerp(start, end float64, t float64) float64 {
return start*(1.0-t) + end*t
}
func LerpPositions(p0, p1 Position, t float64) Position {
return PositionAt(
int(math.Round(Lerp(float64(p0.x), float64(p1.x), t))),
int(math.Round(Lerp(float64(p0.y), float64(p1.y), t))),
)
}

View file

@ -87,6 +87,11 @@ type Entity_DropTableComponent struct {
DropTable *LootTable DropTable *LootTable
} }
type Entity_ProjectileComponent struct {
Source Entity
Path *engine.Path
}
type Entity interface { type Entity interface {
UniqueId() uuid.UUID UniqueId() uuid.UUID
@ -99,6 +104,7 @@ type Entity interface {
Stats() *Entity_StatsHolderComponent Stats() *Entity_StatsHolderComponent
HealthData() *Entity_HealthComponent HealthData() *Entity_HealthComponent
DropTable() *Entity_DropTableComponent DropTable() *Entity_DropTableComponent
Projectile() *Entity_ProjectileComponent
} }
type BaseEntity struct { type BaseEntity struct {
@ -113,6 +119,7 @@ type BaseEntity struct {
stats *Entity_StatsHolderComponent stats *Entity_StatsHolderComponent
damageable *Entity_HealthComponent damageable *Entity_HealthComponent
dropTable *Entity_DropTableComponent dropTable *Entity_DropTableComponent
projectile *Entity_ProjectileComponent
} }
func (be *BaseEntity) UniqueId() uuid.UUID { func (be *BaseEntity) UniqueId() uuid.UUID {
@ -155,6 +162,10 @@ func (be *BaseEntity) DropTable() *Entity_DropTableComponent {
return be.dropTable return be.dropTable
} }
func (be *BaseEntity) Projectile() *Entity_ProjectileComponent {
return be.projectile
}
func CreateEntity(components ...func(*BaseEntity)) *BaseEntity { func CreateEntity(components ...func(*BaseEntity)) *BaseEntity {
e := &BaseEntity{ e := &BaseEntity{
id: uuid.New(), id: uuid.New(),
@ -250,3 +261,12 @@ func WithDropTable(table map[int]ItemSupplier) func(e *BaseEntity) {
} }
} }
} }
func WithProjectileData(source Entity, path *engine.Path) func(e *BaseEntity) {
return func(e *BaseEntity) {
e.projectile = &Entity_ProjectileComponent{
Source: source,
Path: path,
}
}
}

View file

@ -5,11 +5,86 @@ import (
"mvvasilev/last_light/engine" "mvvasilev/last_light/engine"
) )
// func ProjectileBehavior() func(npc Entity) (complete bool, requeue bool) { type ProjectileSprite rune
// return func(npc Entity) (complete bool, requeue bool) {
// } //
// } // \ | /
//
// ─ + ─
//
// / | \
const (
ProjectileSprite_NorthSouth ProjectileSprite = '|'
ProjectileSprite_EastWest = '─'
ProjectileSprite_NorthEastSouthWest = '/'
ProjectileSprite_NorthWestSouthEast = '\\'
)
func ProjectileBehavior(eventLog *engine.GameEventLog, dungeon *Dungeon) func(npc Entity) (complete bool, requeue bool) {
return func(npc Entity) (complete bool, requeue bool) {
hasNext := ProjectileFollowPathNext(npc, eventLog, dungeon)
return !hasNext, false
}
}
func ProjectileFollowPathNext(npc Entity, eventLog *engine.GameEventLog, dungeon *Dungeon) (hasNext bool) {
projectileData := npc.Projectile()
positionData := npc.Positioned()
if projectileData == nil || positionData == nil {
return false
}
path := projectileData.Path
next, hasNext := path.Next()
nextTile := dungeon.CurrentLevel().TileAt(next.XY())
nextTileEntityData := nextTile.Entity()
dungeon.CurrentLevel().DropEntity(npc.UniqueId())
positionData.Position = next
// The next tile is impassable ( wall, void, etc. ) and contains no entity to damage
// This is the end of the path
if nextTileEntityData == nil && !nextTile.Passable() {
return false
}
// Otherwise, if the tile is passible, but also the end of the path, stop here and despawn the projectile
if nextTileEntityData == nil && next == projectileData.Path.To() {
return false
}
// The next tile contains an entity, do damage to it if we have damage data
if nextTileEntityData != nil {
// The arrow strikes against its master, but to no avail, for I decree it to be illegal
if nextTileEntityData.Entity == projectileData.Source {
return
}
// Futher I decree, that should the arrow striketh at thyself, it shall be blocked from doing so
if nextTileEntityData.Entity == npc {
return
}
if projectileData.Source == nil {
return false
}
ExecuteAttack(eventLog, projectileData.Source, nextTileEntityData.Entity)
return false
}
dungeon.CurrentLevel().AddEntity(npc)
return
}
func HostileNPCBehavior(eventLog *engine.GameEventLog, dungeon *Dungeon, player *Player) func(npc Entity) (complete bool, requeue bool) { func HostileNPCBehavior(eventLog *engine.GameEventLog, dungeon *Dungeon, player *Player) func(npc Entity) (complete bool, requeue bool) {
return func(npc Entity) (complete bool, requeue bool) { return func(npc Entity) (complete bool, requeue bool) {
@ -121,6 +196,10 @@ func WithinHitRange(pos engine.Position, otherPos engine.Position) bool {
func ExecuteAttack(eventLog *engine.GameEventLog, attacker, victim Entity) { func ExecuteAttack(eventLog *engine.GameEventLog, attacker, victim Entity) {
hit, precision, evasion, dmg, dmgType := CalculateAttack(attacker, victim) hit, precision, evasion, dmg, dmgType := CalculateAttack(attacker, victim)
if attacker.Projectile() != nil {
attacker = attacker.Projectile().Source
}
attackerName := "Unknown" attackerName := "Unknown"
if attacker.Named() != nil { if attacker.Named() != nil {
@ -138,6 +217,10 @@ func ExecuteAttack(eventLog *engine.GameEventLog, attacker, victim Entity) {
return return
} }
if victim.HealthData() == nil {
return
}
victim.HealthData().Health -= dmg victim.HealthData().Health -= dmg
if victim.HealthData().Health <= 0 { if victim.HealthData().Health <= 0 {

View file

@ -12,10 +12,13 @@ const (
ImpClaws specialItemType = 100_000 + iota ImpClaws specialItemType = 100_000 + iota
) )
func Entity_ArrowProjectile(startX, startY int, targetX, targetY int) Entity { func Entity_ArrowProjectile(source Entity, path *engine.Path, eventLog *engine.GameEventLog, dungeon *Dungeon) Entity {
return CreateEntity( return CreateEntity(
WithName("Arrow"), WithName("Arrow"),
WithPosition(engine.PositionAt(startX, startY)), WithPosition(path.From()),
WithPresentation('?', tcell.StyleDefault),
WithProjectileData(source, path),
WithBehavior(1, ProjectileBehavior(eventLog, dungeon)),
) )
} }

View file

@ -9,6 +9,7 @@ import (
type Player struct { type Player struct {
Entity Entity
inLookState bool
skipNextTurn bool skipNextTurn bool
} }
@ -64,3 +65,11 @@ func (p *Player) SkipNextTurn(skip bool) {
func (p *Player) IsNextTurnSkipped() bool { func (p *Player) IsNextTurnSkipped() bool {
return p.skipNextTurn return p.skipNextTurn
} }
func (p *Player) IsInLookState() bool {
return p.inLookState
}
func (p *Player) SetInLookState(lookState bool) {
p.inLookState = lookState
}

View file

@ -90,7 +90,7 @@ func (d *Dungeon) HasNextLevel() bool {
type DungeonLevel struct { type DungeonLevel struct {
ground Map ground Map
entitiesByPosition map[engine.Position][]Entity entitiesByPosition map[engine.Position]Entity
entities map[uuid.UUID]Entity entities map[uuid.UUID]Entity
} }
@ -142,7 +142,7 @@ func CreateDungeonLevel(width, height int, dungeonType DungeonType) (dLevel *Dun
dLevel = &DungeonLevel{ dLevel = &DungeonLevel{
ground: groundLevel, ground: groundLevel,
entities: map[uuid.UUID]Entity{}, entities: map[uuid.UUID]Entity{},
entitiesByPosition: map[engine.Position][]Entity{}, entitiesByPosition: map[engine.Position]Entity{},
} }
if groundLevel.Rooms() == nil { if groundLevel.Rooms() == nil {
@ -238,11 +238,7 @@ func (d *DungeonLevel) AddEntity(entity Entity) {
d.entities[entity.UniqueId()] = entity d.entities[entity.UniqueId()] = entity
if entity.Positioned() != nil { if entity.Positioned() != nil {
if d.entitiesByPosition[entity.Positioned().Position] == nil { d.entitiesByPosition[entity.Positioned().Position] = entity
d.entitiesByPosition[entity.Positioned().Position] = []Entity{entity}
} else {
d.entitiesByPosition[entity.Positioned().Position] = append(d.entitiesByPosition[entity.Positioned().Position], entity)
}
} }
} }
@ -258,9 +254,7 @@ func (d *DungeonLevel) MoveEntityTo(uuid uuid.UUID, x, y int) {
ent.Positioned().Position = engine.PositionAt(x, y) ent.Positioned().Position = engine.PositionAt(x, y)
if d.entitiesByPosition[ent.Positioned().Position] == nil { if d.entitiesByPosition[ent.Positioned().Position] == nil {
d.entitiesByPosition[ent.Positioned().Position] = []Entity{ent} d.entitiesByPosition[ent.Positioned().Position] = ent
} else {
d.entitiesByPosition[ent.Positioned().Position] = append(d.entitiesByPosition[ent.Positioned().Position], ent)
} }
} }
@ -303,7 +297,7 @@ func (d *DungeonLevel) TileAt(x, y int) Tile {
tile := Map_TileAt(d.ground, x, y) tile := Map_TileAt(d.ground, x, y)
if entity != nil { if entity != nil {
return CreateTileFromPrototype(tile, Tile_WithEntities(entity)) return CreateTileFromPrototype(tile, Tile_WithEntity(entity))
} }
return tile return tile
@ -316,14 +310,14 @@ func (d *DungeonLevel) IsTilePassable(x, y int) bool {
tile := d.TileAt(x, y) tile := d.TileAt(x, y)
if tile.Entities() != nil { if tile.Entity() != nil {
return false return false
} }
return tile.Passable() return tile.Passable()
} }
func (d *DungeonLevel) EntitiesAt(x, y int) (e []Entity) { func (d *DungeonLevel) EntityAt(x, y int) (e Entity) {
return d.entitiesByPosition[engine.PositionAt(x, y)] return d.entitiesByPosition[engine.PositionAt(x, y)]
} }

View file

@ -1,10 +1,7 @@
package model package model
import ( import (
"slices"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/google/uuid"
) )
type Material uint type Material uint
@ -26,7 +23,7 @@ type Tile_ItemComponent struct {
} }
type Tile_EntityComponent struct { type Tile_EntityComponent struct {
Entities []Entity Entity Entity
} }
type Tile interface { type Tile interface {
@ -40,9 +37,9 @@ type Tile interface {
RemoveItem() RemoveItem()
WithItem(item Item) WithItem(item Item)
Entities() *Tile_EntityComponent Entity() *Tile_EntityComponent
RemoveEntity(uuid uuid.UUID) RemoveEntity()
AddEntity(entity Entity) WithEntity(entity Entity)
} }
type BaseTile struct { type BaseTile struct {
@ -52,8 +49,8 @@ type BaseTile struct {
material Material material Material
passable, opaque, transparent bool passable, opaque, transparent bool
item *Tile_ItemComponent item *Tile_ItemComponent
entities *Tile_EntityComponent entity *Tile_EntityComponent
} }
func CreateTileFromPrototype(prototype Tile, components ...func(*BaseTile)) Tile { func CreateTileFromPrototype(prototype Tile, components ...func(*BaseTile)) Tile {
@ -121,46 +118,24 @@ func (t *BaseTile) WithItem(item Item) {
} }
} }
func (t *BaseTile) Entities() *Tile_EntityComponent { func (t *BaseTile) Entity() *Tile_EntityComponent {
return t.entities return t.entity
} }
func (t *BaseTile) RemoveEntity(uuid uuid.UUID) { func (t *BaseTile) WithEntity(entity Entity) {
if t.entities == nil { t.entity = &Tile_EntityComponent{
return Entity: entity,
} }
t.entities.Entities = slices.DeleteFunc(t.entities.Entities, func(e Entity) bool { return e.UniqueId() == uuid })
} }
func (t *BaseTile) AddEntity(entity Entity) { func (t *BaseTile) RemoveEntity() {
if t.entities == nil { t.entity = nil
t.entities = &Tile_EntityComponent{
Entities: []Entity{
entity,
},
}
return
}
t.entities.Entities = append(t.entities.Entities, entity)
} }
func Tile_WithEntity(entity Entity) func(*BaseTile) { func Tile_WithEntity(entity Entity) func(*BaseTile) {
return func(bt *BaseTile) { return func(bt *BaseTile) {
bt.entities = &Tile_EntityComponent{ bt.entity = &Tile_EntityComponent{
Entities: []Entity{ Entity: entity,
entity,
},
}
}
}
func Tile_WithEntities(entities []Entity) func(*BaseTile) {
return func(bt *BaseTile) {
bt.entities = &Tile_EntityComponent{
Entities: entities,
} }
} }
} }

View file

@ -5,6 +5,7 @@ import (
"mvvasilev/last_light/engine" "mvvasilev/last_light/engine"
"mvvasilev/last_light/game/model" "mvvasilev/last_light/game/model"
"mvvasilev/last_light/game/systems" "mvvasilev/last_light/game/systems"
"mvvasilev/last_light/game/ui"
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
@ -23,9 +24,14 @@ type LookState struct {
player *model.Player player *model.Player
dungeon *model.Dungeon dungeon *model.Dungeon
showLog bool
uiEventLog *ui.UIEventLog
showCursor bool showCursor bool
cursorPos engine.Position cursorPos engine.Position
lastCursorBlinkTime time.Time lastCursorBlinkTime time.Time
nextGameState GameState
} }
func CreateLookState(prevState GameState, eventLog *engine.GameEventLog, dungeon *model.Dungeon, inputSystem *systems.InputSystem, turnSystem *systems.TurnSystem, player *model.Player) *LookState { func CreateLookState(prevState GameState, eventLog *engine.GameEventLog, dungeon *model.Dungeon, inputSystem *systems.InputSystem, turnSystem *systems.TurnSystem, player *model.Player) *LookState {
@ -38,6 +44,8 @@ func CreateLookState(prevState GameState, eventLog *engine.GameEventLog, dungeon
eventLog: eventLog, eventLog: eventLog,
cursorPos: engine.PositionAt(0, 0), cursorPos: engine.PositionAt(0, 0),
lastCursorBlinkTime: time.Now(), lastCursorBlinkTime: time.Now(),
showLog: true,
uiEventLog: ui.CreateUIEventLog(0, 17, 80, 7, eventLog, tcell.StyleDefault),
} }
} }
@ -46,6 +54,8 @@ func (ls *LookState) InputContext() systems.InputContext {
} }
func (ls *LookState) OnTick(dt int64) GameState { func (ls *LookState) OnTick(dt int64) GameState {
ls.nextGameState = ls
switch ls.inputSystem.NextAction() { switch ls.inputSystem.NextAction() {
case systems.InputAction_Move_North: case systems.InputAction_Move_North:
ls.cursorPos = ls.cursorPos.WithOffset(model.MovementDirectionOffset(model.North)) ls.cursorPos = ls.cursorPos.WithOffset(model.MovementDirectionOffset(model.North))
@ -55,15 +65,17 @@ func (ls *LookState) OnTick(dt int64) GameState {
ls.cursorPos = ls.cursorPos.WithOffset(model.MovementDirectionOffset(model.East)) ls.cursorPos = ls.cursorPos.WithOffset(model.MovementDirectionOffset(model.East))
case systems.InputAction_Move_West: case systems.InputAction_Move_West:
ls.cursorPos = ls.cursorPos.WithOffset(model.MovementDirectionOffset(model.West)) ls.cursorPos = ls.cursorPos.WithOffset(model.MovementDirectionOffset(model.West))
case systems.InputAction_OpenLogs:
ls.showLog = !ls.showLog
case systems.InputAction_Describe: case systems.InputAction_Describe:
ls.Describe() ls.Describe()
case systems.InputAction_Shoot: case systems.InputAction_Shoot:
ls.ShootEquippedWeapon() ls.ShootEquippedWeapon()
case systems.InputAction_Menu_Exit: case systems.InputAction_Menu_Exit:
return ls.prevState ls.nextGameState = ls.prevState
} }
return ls return ls.nextGameState
} }
func (ls *LookState) ShootEquippedWeapon() { func (ls *LookState) ShootEquippedWeapon() {
@ -90,15 +102,49 @@ func (ls *LookState) ShootEquippedWeapon() {
} }
// TODO: Projectiles // TODO: Projectiles
dX, dY := ls.lookCursorCoordsToDungeonCoords()
distance := engine.PositionAt(dX, dY).Distance(ls.player.Position())
if distance >= 12 {
ls.eventLog.Log("Can't see in the dark that far")
return
}
path := engine.LinePath(
ls.player.Position(),
engine.PositionAt(dX, dY),
)
if path == nil {
ls.eventLog.Log("Can't shoot there, something is in the way")
return
}
projectile := model.Entity_ArrowProjectile(ls.player, path, ls.eventLog, ls.dungeon)
ls.turnSystem.Schedule(
projectile.Behavior().Speed,
projectile.Behavior().Behavior,
)
ls.player.SkipNextTurn(true) ls.player.SkipNextTurn(true)
ls.turnSystem.NextTurn() ls.nextGameState = ls.prevState
} }
func (ls *LookState) Describe() { func (ls *LookState) Describe() {
dX, dY := ls.lookCursorCoordsToDungeonCoords() dX, dY := ls.lookCursorCoordsToDungeonCoords()
distance := engine.PositionAt(dX, dY).Distance(ls.player.Position())
if distance >= 12 {
ls.eventLog.Log("Can't see in the dark that far")
return
}
isVisibleFromPlayer, lastTile := model.HasLineOfSight(ls.dungeon, ls.player.Position(), engine.PositionAt(dX, dY)) isVisibleFromPlayer, lastTile := model.HasLineOfSight(ls.dungeon, ls.player.Position(), engine.PositionAt(dX, dY))
if !isVisibleFromPlayer { if !isVisibleFromPlayer {
@ -110,10 +156,10 @@ func (ls *LookState) Describe() {
tile := ls.dungeon.CurrentLevel().TileAt(dX, dY) tile := ls.dungeon.CurrentLevel().TileAt(dX, dY)
entities := tile.Entities() entity := tile.Entity()
if entities != nil { if entity != nil {
ls.DescribeEntities(entities.Entities) ls.DescribeEntity(entity.Entity)
return return
} }
@ -131,27 +177,25 @@ func (ls *LookState) Describe() {
ls.eventLog.Log(fmt.Sprintf("%s: %s", materialName, materialDesc)) ls.eventLog.Log(fmt.Sprintf("%s: %s", materialName, materialDesc))
} }
func (ls *LookState) DescribeEntities(entities []model.Entity) { func (ls *LookState) DescribeEntity(entity model.Entity) {
if entities == nil { if entity == nil {
return return
} }
for _, entity := range entities { if entity == ls.player {
if entity == ls.player { ls.eventLog.Log("You")
ls.eventLog.Log("You")
continue return
} }
if entity.Named() == nil { if entity.Named() == nil {
continue return
} }
if entity.Described() != nil { if entity.Described() != nil {
ls.eventLog.Log(fmt.Sprintf("%s: %s", entity.Named().Name, entity.Described().Description)) ls.eventLog.Log(fmt.Sprintf("%s: %s", entity.Named().Name, entity.Described().Description))
} else { } else {
ls.eventLog.Log(entity.Named().Name) ls.eventLog.Log(entity.Named().Name)
}
} }
} }
@ -209,5 +253,9 @@ func (ls *LookState) CollectDrawables() []engine.Drawable {
} }
})) }))
if ls.showLog {
drawables = append(drawables, ls.uiEventLog)
}
return drawables return drawables
} }

View file

@ -64,7 +64,7 @@ func CreatePlayingState(turnSystem *systems.TurnSystem, inputSystem *systems.Inp
case systems.InputAction_OpenInventory: case systems.InputAction_OpenInventory:
s.nextGameState = CreateInventoryScreenState(s.eventLog, s.dungeon, s.inputSystem, s.turnSystem, s.player, s) s.nextGameState = CreateInventoryScreenState(s.eventLog, s.dungeon, s.inputSystem, s.turnSystem, s.player, s)
case systems.InputAction_EnterLookMode: case systems.InputAction_EnterLookMode:
s.viewShortLogs = !s.viewShortLogs s.viewShortLogs = false
s.nextGameState = CreateLookState(s, s.eventLog, s.dungeon, s.inputSystem, s.turnSystem, s.player) s.nextGameState = CreateLookState(s, s.eventLog, s.dungeon, s.inputSystem, s.turnSystem, s.player)
case systems.InputAction_PickUpItem: case systems.InputAction_PickUpItem:
complete = PickUpItemUnderPlayer(s.eventLog, s.dungeon, s.player) complete = PickUpItemUnderPlayer(s.eventLog, s.dungeon, s.player)
@ -162,7 +162,7 @@ func (ps *PlayingState) MovePlayer(direction model.Direction) (success bool) {
newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(direction)) newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(direction))
ent := ps.dungeon.CurrentLevel().EntitiesAt(newPlayerPos.XY())[0] ent := ps.dungeon.CurrentLevel().EntityAt(newPlayerPos.XY())
// We are moving into an entity with health data. Attack it. // We are moving into an entity with health data. Attack it.
if ent != nil && ent.HealthData() != nil { if ent != nil && ent.HealthData() != nil {
@ -334,8 +334,8 @@ func (ps *PlayingState) CollectDrawables() []engine.Drawable {
if tile != nil { if tile != nil {
if tile.Entities() != nil { if tile.Entity() != nil {
return tile.Entities().Entities[0].Presentable().Rune, tile.Entities().Entities[0].Presentable().Style return tile.Entity().Entity.Presentable().Rune, tile.Entity().Entity.Presentable().Style
} }
if tile.Item() != nil { if tile.Item() != nil {

View file

@ -93,6 +93,7 @@ func CreateInputSystemWithDefaultBindings() *InputSystem {
InputKeyOf(InputContext_Look, 0, tcell.KeyRight, 0): InputAction_Move_East, InputKeyOf(InputContext_Look, 0, tcell.KeyRight, 0): InputAction_Move_East,
InputKeyOf(InputContext_Look, 0, tcell.KeyRune, 'd'): InputAction_Describe, InputKeyOf(InputContext_Look, 0, tcell.KeyRune, 'd'): InputAction_Describe,
InputKeyOf(InputContext_Look, 0, tcell.KeyRune, 'a'): InputAction_Shoot, InputKeyOf(InputContext_Look, 0, tcell.KeyRune, 'a'): InputAction_Shoot,
InputKeyOf(InputContext_Look, 0, tcell.KeyRune, 'l'): InputAction_OpenLogs,
InputKeyOf(InputContext_Look, 0, tcell.KeyESC, 0): InputAction_Menu_Exit, InputKeyOf(InputContext_Look, 0, tcell.KeyESC, 0): InputAction_Menu_Exit,
}, },
} }