Enemy info view.

This commit is contained in:
Daniel Schmidt 2023-04-27 20:01:22 +02:00
parent 2de7a1a65c
commit 61b4953a6a
8 changed files with 209 additions and 74 deletions

View File

@ -34,8 +34,8 @@ register_enemy(
description = "Loves to eat metal.",
look = "/v\\",
color = "#e6e65a",
initial_hp = 16,
max_hp = 16,
initial_hp = 22,
max_hp = 22,
gold = 10,
callbacks = {
on_turn = function(ctx)
@ -60,8 +60,8 @@ register_enemy(
(* *)
)#(]],
color = "#32a891",
initial_hp = 22,
max_hp = 22,
initial_hp = 25,
max_hp = 25,
gold = 15,
callbacks = {
on_player_turn = function(ctx)

View File

@ -2,6 +2,33 @@
-- Base Events
--
register_event(
"MERCHANT",
{
name = "A strange figure",
description = [[The merchant is a tall, lanky figure draped in a long, tattered coat made of plant fibers and animal hides. Their face is hidden behind a mask made of twisted roots and vines, giving them an unsettling, almost alien appearance.
Despite their strange appearance, the merchant is a shrewd negotiator and a skilled trader. They carry with them a collection of bizarre and exotic items, including plant-based weapons, animal pelts, and strange, glowing artifacts that seem to pulse with an otherworldly energy.
The merchant is always looking for a good deal, and they're not above haggling with potential customers...]],
choices = {
{
description = "Trade",
callback = function()
return GAME_STATE_MERCHANT
end
},
{
description = "Pass",
callback = function()
return GAME_STATE_RANDOM
end
}
},
on_end = function(choice) return nil end,
}
)
register_event(
"START",
{
@ -66,6 +93,10 @@ As you struggle to gather your bearings, you notice a blinking panel on the wall
}
)
--
-- Stage 1 Entrance
--
register_event(
"FIRST_OUTSIDE",
{
@ -90,72 +121,77 @@ You take a deep breath of the fresh air, feeling the warmth of the sun on your f
)
register_event(
"CHOICE",
"THE_WASTELAND",
{
name = "A choice",
description = [[Some challenges are behind you. Choose wisely...]],
name = "The Wasteland",
description = [[
```
|==| ~
_.,-*~'^'~*-,._ ( ~ _.,-*~'^'~*-,._ ~ (())
| '*-,._ __ _.,-*' '-,._ | x|
| '*-,._/.,-*' '*-,|x_|,-*,
| |
`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'
```
You finally find a way leading to the outside, and with a deep breath, you step out into the unforgiving wasteland.
The scorching sun beats down on you as the sand whips against your skin, a reminder of the horrors that have befallen the world. In the distance, the remains of once-great cities jut up from the ground like jagged teeth, now nothing more than crumbling ruins. The air is thick with the acrid smell of decay and the oppressive silence is only broken by the occasional howl of some mutated creature. As you take your first steps into this new world, you realize that survival will not be easy, and that the journey ahead will be fraught with danger at every turn...]],
choices = {
{
description = "Meet the Merchant",
callback = function()
return GAME_STATE_MERCHANT
end
},
{
description = "Remove a Card",
callback = function()
set_event("CARD_REMOVE")
return GAME_STATE_EVENT
end
},
{
description = "Gain a Card",
callback = function()
set_event("CARD_GAIN")
return GAME_STATE_EVENT
end
},
{
description = "Rest & Heal",
callback = function()
set_event("REST")
return GAME_STATE_EVENT
end
},
{
description = "Pass",
callback = function()
return GAME_STATE_RANDOM
end
description = "Go...",
callback = function() return nil end
}
},
on_end = function(choice) return nil end,
on_end = function()
return GAME_STATE_RANDOM
end,
}
)
register_event(
"MERCHANT",
"THE_CORE",
{
name = "A strange figure",
description = [[The merchant is a tall, lanky figure draped in a long, tattered coat made of plant fibers and animal hides. Their face is hidden behind a mask made of twisted roots and vines, giving them an unsettling, almost alien appearance.
name = "The Wasteland",
description = [[
```
________ ______
/_ __/ /_ ___ / ____/___ ________
/ / / __ \/ _ \ / / / __ \/ ___/ _ \
/ / / / / / __/ / /___/ /_/ / / / __/
/_/ /_/ /_/\___/ \____/\____/_/ \___/ ...
Despite their strange appearance, the merchant is a shrewd negotiator and a skilled trader. They carry with them a collection of bizarre and exotic items, including plant-based weapons, animal pelts, and strange, glowing artifacts that seem to pulse with an otherworldly energy.
```
You finally find a way you thought would lead to the outside, only to discover that you're still inside the massive facility known as "The Core."
The merchant is always looking for a good deal, and they're not above haggling with potential customers...]],
As you step out of the cryo facility, the eerie silence is broken by the sound of metal scraping against metal and distant whirring of malfunctioning machinery. The flickering lights and sparks from faulty wires cast a sickly glow on the cold metal walls. You realize that this place is not as deserted as you initially thought, and the unsettling feeling in your gut only grows stronger as you make your way through the dimly lit corridors, surrounded by the echoes of your own footsteps and the sound of flickering computer screens.]],
choices = {
{
description = "Trade",
callback = function()
return GAME_STATE_MERCHANT
end
},
{
description = "Pass",
callback = function()
return GAME_STATE_RANDOM
end
description = "Go...",
callback = function() return nil end
}
},
on_end = function(choice) return nil end,
on_end = function()
return GAME_STATE_RANDOM
end,
}
)
register_event(
"BIO_KINGDOM",
{
name = "Bio Kingdom",
description = [[You finally find a way leading to the outside, and step out of the cryo facility into a world you no longer recognize.
The air is thick with humidity and the sounds of the jungle are overwhelming. Strange, mutated plants tower over you, their vines twisting and tangling around each other in a macabre dance. The colors of the leaves and flowers are sickly, a greenish hue that reminds you of illness rather than life. The ruins of buildings are visible in the distance, swallowed up by the overgrowth. You can hear the chirping and buzzing of insects, but it's mixed with something else - something that sounds almost like whispers or moans. The "jungle" seems to be alive, but not in any way that you would have imagined.]],
choices = {
{
description = "Go...",
callback = function() return nil end
}
},
on_end = function()
return GAME_STATE_RANDOM
end,
}
)

View File

@ -8,7 +8,7 @@ register_status_effect("WEAKEN", {
look = "W",
foreground = "#ed985f",
state = function()
return "-" .. tostring(ctx.stacks * 2) .. " damage"
return "Deals " .. highlight(ctx.stacks * 2) .. " less damage"
end,
can_stack = true,
decay = DECAY_ALL,
@ -29,7 +29,7 @@ register_status_effect("VULNERABLE", {
look = "Vur",
foreground = "#ffba08",
state = function(ctx)
return tostring(ctx.stacks * 25) .. "% more damage"
return "Takes " .. highlight(ctx.stacks * 25) .. "% more damage"
end,
can_stack = true,
decay = DECAY_ALL,
@ -50,7 +50,7 @@ register_status_effect("BURN", {
look = "Brn",
foreground = "#d00000",
state = function(ctx)
return tostring(ctx.stacks * 4) .. " damage per turn"
return "Takes " .. highlight(ctx.stacks * 4) .. " damage per turn"
end,
can_stack = true,
decay = DECAY_ALL,
@ -73,7 +73,7 @@ register_status_effect("STRENGTH", {
look = "Str",
foreground = "#d00000",
state = function(ctx)
return tostring(ctx.stacks) .. " damage"
return "Deal " .. highlight(ctx.stacks) .. " more damage"
end,
can_stack = true,
decay = DECAY_ALL,
@ -94,7 +94,7 @@ register_status_effect("BLOCK", {
look = "Blk",
foreground = "#219ebc",
state = function(ctx)
return tostring(ctx.stacks) .. " damage reduced"
return "Takes " .. highlight(ctx.stacks) .. " less damage"
end,
can_stack = true,
decay = DECAY_ALL,
@ -117,7 +117,7 @@ register_status_effect("RITUAL", {
look = "Rit",
foreground = "#bb3e03",
state = function(ctx)
return tostring(ctx.stacks * 25) .. "% more damage"
return nil
end,
can_stack = true,
decay = DECAY_NONE,
@ -136,7 +136,7 @@ register_status_effect("FEAR", {
look = "Fear",
foreground = "#bb3e03",
state = function(ctx)
return "Can't act for " .. highlight(tostring(ctx.stacks)) .. " turns"
return "Can't act for " .. highlight(ctx.stacks) .. " turns"
end,
can_stack = true,
decay = DECAY_ONE,
@ -150,7 +150,7 @@ register_status_effect("FEAR", {
register_status_effect("BLEED", {
name = "Bleed",
description = "You are losing some red sauce.",
description = "Losing some red sauce.",
look = "Bld",
foreground = "#ff0000",
state = function(ctx)

View File

@ -6,9 +6,11 @@ end, registered.card))
-- Pre-Stage
--
stage_1_init_events = { "THE_CORE", "BIO_KINGDOM", "THE_WASTELAND" }
register_story_teller("PRE_STAGE", {
active = function(ctx)
if not had_event("FIRST_OUTSIDE") then
if not had_events_any(stage_1_init_events) then
return 1
end
return 0
@ -19,8 +21,11 @@ register_story_teller("PRE_STAGE", {
if stage >= 3 then
-- If we didn't skip the pre-stage we get another artifact
set_event(create_artifact_choice({ random_artifact(get_merchant_gold_max()), random_artifact(get_merchant_gold_max()) }, {
description = [[As you explore the abandoned cryo facility, a feeling of dread washes over you. The facility is eerily quiet, with malfunctioning computers and flickering lights being the only signs of life. As you move through the winding corridors, you stumble upon a hidden door. It's almost as if the facility itself is trying to keep you from finding what lies beyond.
After some effort, you manage to open the door and find yourself in a small room. The room is dark, and you can barely make out a chest in the center of the room. As you approach it, the feeling of unease grows stronger. What secret artifact could be hidden inside this chest? Is it something that will aid you on your journey or something more sinister? You take a deep breath, steeling yourself for whatever you may find inside, and reach for the lid...]],
on_end = function()
set_event("FIRST_OUTSIDE")
set_event(stage_1_init_events[math.random(#stage_1_init_events)])
return GAME_STATE_EVENT
end
}))
@ -54,7 +59,7 @@ stage_2 = {
register_story_teller("STAGE_1", {
active = function(ctx)
if had_event("FIRST_OUTSIDE") then
if had_events_any(stage_1_init_events) then
return 1
end
return 0
@ -86,7 +91,7 @@ register_story_teller("STAGE_1", {
register_story_teller("STAGE_2", {
active = function(ctx)
if had_event("FIRST_OUTSIDE") and get_stages_cleared() > 10 then
if had_events_any(stage_1_init_events) and get_stages_cleared() > 10 then
return 2
end
return 0
@ -108,7 +113,7 @@ register_story_teller("STAGE_2", {
register_story_teller("STAGE_3", {
active = function(ctx)
if had_event("FIRST_OUTSIDE") and get_stages_cleared() > 20 then
if had_events_any(stage_1_init_events) and get_stages_cleared() > 20 then
return 3
end
return 0

View File

@ -208,6 +208,28 @@ fun = require "fun"
return 1
}))
l.SetGlobal("had_events", l.NewFunction(func(state *lua.LState) int {
var ids []string
if err := mapper.Map(state.Get(1).(*lua.LTable), &ids); err != nil {
session.logLuaError("had_event", "", err)
return 0
} else {
state.Push(luhelp.ToLua(state, session.HadEvents(ids)))
}
return 1
}))
l.SetGlobal("had_events_any", l.NewFunction(func(state *lua.LState) int {
var ids []string
if err := mapper.Map(state.Get(1).(*lua.LTable), &ids); err != nil {
session.logLuaError("had_events_any", "", err)
return 0
} else {
state.Push(luhelp.ToLua(state, session.HadEvents(ids)))
}
return 1
}))
// Actor Operations
l.SetGlobal("get_player", l.NewFunction(func(state *lua.LState) int {

View File

@ -622,6 +622,16 @@ func (s *Session) HadEvent(id string) bool {
return lo.Contains(s.eventHistory, id)
}
// HadEvents checks if the given events already happened in this run.
func (s *Session) HadEvents(ids []string) bool {
return lo.Every(s.eventHistory, ids)
}
// HadEventsAny checks if at least one of the given events already happened in this run.
func (s *Session) HadEventsAny(ids []string) bool {
return lo.Some(s.eventHistory, ids)
}
// GetEventHistory returns the ordered list of all events encountered so far.
func (s *Session) GetEventHistory() []string {
return s.eventHistory
@ -916,6 +926,29 @@ func (s *Session) GetStatusEffect(guid string) *StatusEffect {
return nil
}
// GetStatusEffectState returns the rendered state of the status effect.
func (s *Session) GetStatusEffectState(guid string) string {
status := s.GetStatusEffect(guid)
if status == nil {
return ""
}
instance := s.GetStatusEffectInstance(guid)
if status.State == nil {
return status.Description
}
res, err := status.State.Call(CreateContext("type_id", status.ID, "guid", guid, "stacks", instance.Stacks, "owner", instance.Owner))
if err != nil {
s.logLuaError("State", instance.TypeID, err)
}
if res == nil {
return status.Description
}
return res.(string)
}
// GetStatusEffectInstance returns status effect instance by guid.
func (s *Session) GetStatusEffectInstance(guid string) StatusEffectInstance {
return s.instances[guid].(StatusEffectInstance)

View File

@ -126,6 +126,9 @@ func (m DamageAnimationModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
// TODO: remove in favor of components.Actor
var faceStyle = lipgloss.NewStyle().Border(lipgloss.OuterHalfBlockBorder()).Padding(0, 1).Margin(0, 0, 1, 0).BorderForeground(style.BaseGrayDarker).Foreground(style.BaseRed)
func (m DamageAnimationModel) View() string {
hp := m.startHp - lo.SumBy(
lo.Filter(m.damages, func(_ game.StateEventDamageData, i int) bool {

View File

@ -34,6 +34,7 @@ type Model struct {
selectedCard int
selectedOpponent int
inOpponentSelection bool
inEnemyView bool
animations []tea.Model
event tea.Model
@ -89,8 +90,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case tea.KeyEscape:
// Switch to menu
if m.inOpponentSelection {
if m.inOpponentSelection || m.inEnemyView {
m.inOpponentSelection = false
m.inEnemyView = false
} else {
return overview.New(m, m.zones, m.Session), nil
}
@ -137,7 +139,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case game.GameStateFight:
if m.inOpponentSelection {
for i := 0; i < m.Session.GetOpponentCount(game.PlayerActorID); i++ {
if cardZone := m.zones.Get(fmt.Sprintf("%s%d", ZoneEnemy, i)); cardZone.InBounds(msg) {
if m.zones.Get(fmt.Sprintf("%s%d", ZoneEnemy, i)).InBounds(msg) {
if msg.Type == tea.MouseLeft && m.selectedOpponent == i {
m = m.tryCast()
} else {
@ -148,7 +150,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} else {
onCard := false
for i := 0; i < len(m.Session.GetFight().Hand); i++ {
if cardZone := m.zones.Get(fmt.Sprintf("%s%d", ZoneCard, i)); cardZone.InBounds(msg) {
if m.zones.Get(fmt.Sprintf("%s%d", ZoneCard, i)).InBounds(msg) {
onCard = true
if msg.Type == tea.MouseLeft && m.selectedCard == i {
m = m.tryCast()
@ -161,6 +163,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !onCard && msg.Type == tea.MouseMotion {
m.selectedCard = -1
}
if !m.inOpponentSelection && msg.Type == tea.MouseLeft {
for i := 0; i < m.Session.GetOpponentCount(game.PlayerActorID); i++ {
if m.zones.Get(fmt.Sprintf("%s%d", ZoneEnemy, i)).InBounds(msg) {
m.selectedOpponent = i
m.inEnemyView = true
}
}
}
}
}
@ -228,6 +239,15 @@ func (m Model) View() string {
switch m.Session.GetGameState() {
case game.GameStateFight:
if m.inEnemyView {
return lipgloss.JoinVertical(
lipgloss.Top,
m.fightStatusTop(),
m.fightEnemyInspectView(),
m.fightStatusBottom(),
)
}
return lipgloss.JoinVertical(
lipgloss.Top,
m.fightStatusTop(),
@ -367,7 +387,7 @@ func (m Model) fightStatusBottom() string {
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(style.BaseWhite)).Padding(0, 4, 0, 4).Render(fmt.Sprintf("Deck: %d", len(fight.Deck))),
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FFFF00")).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Used: %d", len(fight.Used))),
lipgloss.NewStyle().Bold(true).Foreground(style.BaseRed).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Exhausted: %d", len(fight.Exhausted))),
lipgloss.NewStyle().Bold(true).Foreground(style.BaseGreen).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Action Points: %d / %d", fight.CurrentPoints, game.PointsPerRound)),
lipgloss.NewStyle().Bold(true).Foreground(style.BaseGreen).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Action Points: %d", fight.CurrentPoints)),
components.StatusEffects(m.Session, m.Session.GetPlayer()),
),
),
@ -388,11 +408,27 @@ func (m Model) fightCardViewHeight() int {
return m.Size.Height - m.fightEnemyViewHeight() - 1 - 4 - 4
}
var faceStyle = lipgloss.NewStyle().Border(lipgloss.OuterHalfBlockBorder()).Padding(0, 1).Margin(0, 0, 1, 0).BorderForeground(style.BaseGrayDarker).Foreground(style.BaseRed)
func (m Model) fightEnemyInspectView() string {
enemy := m.Session.GetOpponents(game.PlayerActorID)[m.selectedOpponent]
status := "Status Effects:\n\n" + strings.Join(lo.Map(enemy.StatusEffects.ToSlice(), func(guid string, index int) string {
return components.StatusEffect(m.Session, guid) + ": " + m.Session.GetStatusEffectState(guid)
}), "\n\n")
return lipgloss.Place(m.Size.Width, m.fightEnemyViewHeight()+m.fightCardViewHeight()+1, lipgloss.Center, lipgloss.Center,
lipgloss.NewStyle().Border(lipgloss.ThickBorder(), true).Padding(1, 2).BorderForeground(style.BaseRedDarker).Render(
lipgloss.JoinHorizontal(lipgloss.Top,
lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, true, false, false).BorderForeground(style.BaseGrayDarker).Padding(0, 2, 2, 0).Render(components.Actor(m.Session, enemy, m.Session.GetEnemy(enemy.TypeID), true, true, false)),
lipgloss.NewStyle().Margin(0, 0, 0, 3).Width(30).Render(status),
),
),
lipgloss.WithWhitespaceChars("?"), lipgloss.WithWhitespaceForeground(style.BaseGrayDarker),
)
}
func (m Model) fightEnemyView() string {
enemyBoxes := lo.Map(m.Session.GetOpponents(game.PlayerActorID), func(actor game.Actor, i int) string {
return components.Actor(m.Session, actor, m.Session.GetEnemy(actor.TypeID), true, true, m.inOpponentSelection && i == m.selectedOpponent)
return components.Actor(m.Session, actor, m.Session.GetEnemy(actor.TypeID), true, true, m.inOpponentSelection && i == m.selectedOpponent || m.zones.Get(fmt.Sprintf("%s%d", ZoneEnemy, i)).InBounds(m.LastMouse))
})
enemyBoxes = lo.Map(enemyBoxes, func(item string, i int) string {