diff --git a/assets/gen/loading_lines.txt b/assets/gen/loading_lines.txt new file mode 100644 index 0000000..c899033 --- /dev/null +++ b/assets/gen/loading_lines.txt @@ -0,0 +1,9 @@ +remember to use your consumables +watch out for the intent of your enemies +collect gold to spend at the merchant +may your death be a glorious one +rise and shine +smell the ashes +welcome back from sleep +the right crowbar in the wrong place... +body slots like HND or ARM can only hold 1 artifact \ No newline at end of file diff --git a/assets/scripts/events/act_0/enemies.lua b/assets/scripts/events/act_0/enemies.lua index 42c65c7..413c3e8 100644 --- a/assets/scripts/events/act_0/enemies.lua +++ b/assets/scripts/events/act_0/enemies.lua @@ -1,10 +1,10 @@ register_event("RUST_MITE", { name = "Tasty metals...", description = [[ - You are walking through the facility hoping to find a way out. After a few turns you hear a strange noise. You look around and come across a strange being. - It seems to be eating the metal from the walls. It looks at you and after a few seconds it rushes towards you. - - **It seems to be hostile!** +You are walking through the facility hoping to find a way out. After a few turns you hear a strange noise. You look around and come across a strange being. +It seems to be eating the metal from the walls. It looks at you and after a few seconds it rushes towards you. + +**It seems to be hostile!** ]], tags = {"ACT_0"}, choices = { @@ -21,11 +21,11 @@ register_event("RUST_MITE", { register_event("CLEAN_BOT", { name = "Corpse. Clean. Engage.", description = [[ - While exploring the facility you hear a strange noise. Suddenly a strange robot appears from one of the corridors. - It seems to be cleaning up the area, but it's not working properly anymore and you can see small sparks coming out of it. - It looks at you and says "Corpse. Clean. Engage.". - - **You're not sure what it means, but it doesn't seem to be friendly!** +While exploring the facility you hear a strange noise. Suddenly a strange robot appears from one of the corridors. +It seems to be cleaning up the area, but it's not working properly anymore and you can see small sparks coming out of it. +It looks at you and says "Corpse. Clean. Engage.". + +**You're not sure what it means, but it doesn't seem to be friendly!** ]], tags = {"ACT_0"}, choices = { diff --git a/game/artifact.go b/game/artifact.go index c8e1215..3701447 100644 --- a/game/artifact.go +++ b/game/artifact.go @@ -3,6 +3,8 @@ package game import ( "encoding/gob" "github.com/BigJk/end_of_eden/internal/lua/luhelp" + "github.com/samber/lo" + "strings" ) func init() { @@ -21,6 +23,12 @@ type Artifact struct { BaseGame bool } +func (a Artifact) PublicTags() []string { + return lo.Filter(a.Tags, func(s string, i int) bool { + return !strings.HasPrefix(s, "_") + }) +} + type ArtifactInstance struct { TypeID string GUID string diff --git a/game/card.go b/game/card.go index d31c6b6..0a76a67 100644 --- a/game/card.go +++ b/game/card.go @@ -3,6 +3,8 @@ package game import ( "encoding/gob" "github.com/BigJk/end_of_eden/internal/lua/luhelp" + "github.com/samber/lo" + "strings" ) func init() { @@ -28,6 +30,12 @@ type Card struct { BaseGame bool } +func (c Card) PublicTags() []string { + return lo.Filter(c.Tags, func(s string, i int) bool { + return !strings.HasPrefix(s, "_") + }) +} + // CardInstance represents an instance of a card owned by some actor. type CardInstance struct { TypeID string diff --git a/ui/components/artifact.go b/ui/components/artifact.go index 54029d9..e574911 100644 --- a/ui/components/artifact.go +++ b/ui/components/artifact.go @@ -28,7 +28,7 @@ func ArtifactCard(session *game.Session, guid string, baseHeight int, width int) Background(lipgloss.Color("#343a40")). Foreground(style.BaseWhite) - tagsText := strings.Join(art.Tags, ", ") + tagsText := strings.Join(art.PublicTags(), ", ") return artifactStyle. Height(baseHeight). diff --git a/ui/components/card.go b/ui/components/card.go index 53e98e2..3d4c800 100644 --- a/ui/components/card.go +++ b/ui/components/card.go @@ -24,7 +24,7 @@ func HalfCard(session *game.Session, guid string, active bool, baseHeight int, m cardState := session.GetCardState(guid) pointText := strings.Repeat("•", card.PointCost) - tagsText := strings.Join(card.Tags, ", ") + tagsText := strings.Join(card.PublicTags(), ", ") cardCol, _ := colorful.Hex(card.Color) bgCol, _ := colorful.MakeColor(style.BaseGrayDarker) diff --git a/ui/menus/gameview/gameview.go b/ui/menus/gameview/gameview.go index 9640d99..7454173 100644 --- a/ui/menus/gameview/gameview.go +++ b/ui/menus/gameview/gameview.go @@ -55,10 +55,12 @@ func New(parent tea.Model, zones *zone.Manager, session *game.Session) Model { session.Log(game.LogTypeSuccess, "Game started! Good luck...") return Model{ - zones: zones, - parent: parent, - event: eventview.New(zones, session), - merchant: merchant.New(zones, session), + zones: zones, + parent: parent, + event: eventview.New(zones, session), + merchant: merchant.New(zones, session), + lastGameState: session.GetGameState(), + lastEvent: session.GetEventID(), Session: session, Start: session.MarkState(), @@ -242,7 +244,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } - switch m.Session.GetGameState() { + currentState := m.Session.GetGameState() + currentEvent := m.Session.GetEventID() + + switch currentState { case game.GameStateFight: case game.GameStateMerchant: m.merchant, cmd = m.merchant.Update(msg) @@ -254,12 +259,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return gameover.New(m.zones, m.Session, m.Start), nil } - if m.Session.GetGameState() != m.lastGameState || m.Session.GetEventID() != m.lastEvent { + // + // Show "New Artifacts" / "New Cards" if there are any. + // + + if currentState != m.lastGameState || currentEvent != m.lastEvent { diff := m.BeforeStateSwitch.Diff(m.Session) m.BeforeStateSwitch = m.Session.MarkState() - m.lastGameState = m.Session.GetGameState() - m.lastEvent = m.Session.GetEventID() + m.lastGameState = currentState + m.lastEvent = currentEvent if len(diff) > 0 { artifacts := lo.Map(lo.Filter(diff, func(item game.StateCheckpoint, index int) bool { diff --git a/ui/menus/mainmenu/mainmenu.go b/ui/menus/mainmenu/mainmenu.go index 60692b3..945a577 100644 --- a/ui/menus/mainmenu/mainmenu.go +++ b/ui/menus/mainmenu/mainmenu.go @@ -112,7 +112,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { log.Println("Error loading save:", err) } else { m.choices = m.choices.Clear() - return gameview.New(m, m.zones, session), cmd + return m, tea.Sequence( + cmd, + root.Push(gameview.New(m, m.zones, session)), + ) } } @@ -131,11 +134,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { })...) m.choices = m.choices.Clear() - return m, root.Push(gameview.New(m, m.zones, game.NewSession( - game.WithLogging(log.New(f, "SESSION ", log.Ldate|log.Ltime|log.Lshortfile)), - game.WithMods(m.settings.GetStrings("mods")), - lo.Ternary(os.Getenv("EOE_DEBUG") == "1", game.WithDebugEnabled(8272), nil), - ))) + return m, tea.Sequence( + cmd, + root.Push(gameview.New(m, m.zones, game.NewSession( + game.WithLogging(log.New(f, "SESSION ", log.Ldate|log.Ltime|log.Lshortfile)), + game.WithMods(m.settings.GetStrings("mods")), + lo.Ternary(os.Getenv("EOE_DEBUG") == "1", game.WithDebugEnabled(8272), nil), + ))), + ) case ChoiceAbout: audio.Play("btn_menu") diff --git a/ui/menus/overview/overview.go b/ui/menus/overview/overview.go index f765fde..d359397 100644 --- a/ui/menus/overview/overview.go +++ b/ui/menus/overview/overview.go @@ -6,6 +6,7 @@ import ( "github.com/BigJk/end_of_eden/system/audio" "github.com/BigJk/end_of_eden/ui" "github.com/BigJk/end_of_eden/ui/components" + "github.com/BigJk/end_of_eden/ui/root" "github.com/BigJk/end_of_eden/ui/style" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" @@ -143,14 +144,14 @@ func (m MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Update card table m.cardTable.SetRows(lo.Map(cards, func(guid string, index int) table.Row { card, instance := m.Session.GetCard(guid) - return table.Row{m.zones.Mark(ZoneCards+fmt.Sprint(index), card.Name), strings.Join(card.Tags, ", "), fmt.Sprint(instance.Level)} + return table.Row{m.zones.Mark(ZoneCards+fmt.Sprint(index), card.Name), strings.Join(card.PublicTags(), ", "), fmt.Sprint(instance.Level)} })) m.cardTable.SetHeight(m.Size.Height - style.HeaderStyle.GetVerticalFrameSize() - 1 - 2) // Update artifact table m.artifactTable.SetRows(lo.Map(artifacts, func(guid string, index int) table.Row { art, _ := m.Session.GetArtifact(guid) - return table.Row{m.zones.Mark(ZoneArtifacts+fmt.Sprint(index), art.Name), strings.Join(art.Tags, ", "), fmt.Sprintf("%d$", art.Price)} + return table.Row{m.zones.Mark(ZoneArtifacts+fmt.Sprint(index), art.Name), strings.Join(art.PublicTags(), ", "), fmt.Sprintf("%d$", art.Price)} })) m.artifactTable.SetHeight(m.Size.Height - style.HeaderStyle.GetVerticalFrameSize() - 1 - 2) @@ -190,7 +191,7 @@ func (m MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyEnter: if m.list.SelectedItem().(choiceItem).key == ChoiceQuit { m.Session.Close() - return nil, nil + return nil, root.RemovePushTransitionFunc() } case tea.KeyDown: fallthrough diff --git a/ui/menus/transition/transition.go b/ui/menus/transition/transition.go new file mode 100644 index 0000000..6555863 --- /dev/null +++ b/ui/menus/transition/transition.go @@ -0,0 +1,73 @@ +package transition + +import ( + "github.com/BigJk/end_of_eden/system/gen" + "github.com/BigJk/end_of_eden/ui" + "github.com/BigJk/end_of_eden/ui/root" + "github.com/BigJk/end_of_eden/ui/style" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "strings" + "time" +) + +const ( + charFull = "●" + charHalfLeft = "◐" + charHalfRight = "◑" + charEmpty = "○" +) + +type TickMsg string + +type Model struct { + ui.MenuBase + + parent tea.Model + created time.Time + line string +} + +func New(parent tea.Model) Model { + return Model{parent: parent, line: gen.GetRandom("loading_lines")} +} + +func (m Model) Start() tea.Msg { + m.created = time.Now() + return m +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.Size = msg + case root.ModelGettingVisibleMsg: + m.created = time.Now() + case TickMsg: + if !m.created.IsZero() && time.Since(m.created) > time.Millisecond*800 { + return m.parent, tea.Sequence(root.GettingVisible(), func() tea.Msg { + return tea.WindowSizeMsg{Width: m.Size.Width, Height: m.Size.Height} + }) + } + } + + return m, tea.Tick(time.Second/30, func(t time.Time) tea.Msg { + return TickMsg("tick") + }) +} + +func (m Model) View() string { + elapsed := time.Since(m.created) + + spinner := strings.Split(strings.Repeat(charEmpty, 10), "") + pos := elapsed.Milliseconds() / 80 % int64(len(spinner)) + spinner[pos] = charFull + spinner[(pos+1)%int64(len(spinner))] = charHalfLeft + spinner[((pos-1)+int64(len(spinner)))%int64(len(spinner))] = charHalfRight + + return lipgloss.Place(m.Size.Width, m.Size.Height, lipgloss.Center, lipgloss.Center, lipgloss.JoinVertical(lipgloss.Center, m.line+"\n", style.RedText.Render(strings.Join(spinner, "")))) +} diff --git a/ui/root/messages.go b/ui/root/messages.go index 24f0733..379965d 100644 --- a/ui/root/messages.go +++ b/ui/root/messages.go @@ -29,37 +29,18 @@ func GettingVisible() tea.Cmd { } } -// Tooltip represents a tooltip aka overlay message that should be displayed. -type Tooltip struct { - ID string - Content string - X int - Y int -} +type PushTransitionFuncMsg func(parent tea.Model) tea.Model -type TooltipMsg Tooltip - -// TooltipCreate creates a new tooltip. -func TooltipCreate(tip Tooltip) tea.Cmd { +// PushTransitionFunc pushes a new transition model on the root ui that will be shown between models on the stack. +func PushTransitionFunc(fn func(parent tea.Model) tea.Model) tea.Cmd { return func() tea.Msg { - return TooltipMsg(tip) + return PushTransitionFuncMsg(fn) } } -type TooltipDeleteMsg string - -// TooltipDelete deletes a tooltip. -func TooltipDelete(id string) tea.Cmd { +// RemovePushTransitionFunc removes the transition model from the root ui. +func RemovePushTransitionFunc() tea.Cmd { return func() tea.Msg { - return TooltipDeleteMsg(id) - } -} - -type TooltipClearMsg struct{} - -// TooltipClear clears all tooltips. -func TooltipClear() tea.Cmd { - return func() tea.Msg { - return TooltipClearMsg(struct{}{}) + return PushTransitionFuncMsg(nil) } } diff --git a/ui/root/root.go b/ui/root/root.go index ddf36de..17280d7 100644 --- a/ui/root/root.go +++ b/ui/root/root.go @@ -14,10 +14,11 @@ import ( // the zone manager. The top model of the internal stack is the current model // and will be rendered. type Model struct { - zones *zone.Manager - stack []tea.Model - size tea.WindowSizeMsg - tooltips map[string]Tooltip + zones *zone.Manager + stack []tea.Model + size tea.WindowSizeMsg + tooltips map[string]Tooltip + transitionModel func(parent tea.Model) tea.Model } // New creates a new root model. @@ -31,7 +32,11 @@ func New(zones *zone.Manager, root tea.Model) Model { // PushModel pushes a new model on the stack. func (m Model) PushModel(model tea.Model) Model { - m.stack = append(m.stack, model) + if m.transitionModel != nil { + m.stack = append(m.stack, m.transitionModel(model)) + } else { + m.stack = append(m.stack, model) + } m.tooltips = map[string]Tooltip{} return m } @@ -71,6 +76,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m = m.PushModel(model) } cmds = append(cmds, GettingVisible()) + case PushTransitionFuncMsg: + m.transitionModel = msg } curIndex := len(m.stack) - 1 @@ -82,22 +89,26 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } - if menu, ok := m.stack[curIndex].(ui.Menu); ok && !menu.HasSize() { - return m, tea.Batch(cmd, func() tea.Msg { - return m.size - }) - } - if m.stack[curIndex] == nil { // If we remove the top model, we need to send a window size message to the new top model // to avoid the layout to be broken. + cmds = append(cmds, + func() tea.Msg { + return tea.WindowSizeMsg{ + Width: m.size.Width, + Height: m.size.Height, + } + }, + GettingVisible(), + ) + m.stack = m.stack[:len(m.stack)-1] + } else if menu, ok := m.stack[curIndex].(ui.Menu); ok && !menu.HasSize() { cmds = append(cmds, func() tea.Msg { return tea.WindowSizeMsg{ Width: m.size.Width, Height: m.size.Height, } - }, GettingVisible()) - m.stack = m.stack[:len(m.stack)-1] + }) } return m, tea.Batch(cmds...) diff --git a/ui/root/tooltip.go b/ui/root/tooltip.go new file mode 100644 index 0000000..903cf2f --- /dev/null +++ b/ui/root/tooltip.go @@ -0,0 +1,38 @@ +package root + +import tea "github.com/charmbracelet/bubbletea" + +// Tooltip represents a tooltip aka overlay message that should be displayed. +type Tooltip struct { + ID string + Content string + X int + Y int +} + +type TooltipMsg Tooltip + +// TooltipCreate creates a new tooltip. +func TooltipCreate(tip Tooltip) tea.Cmd { + return func() tea.Msg { + return TooltipMsg(tip) + } +} + +type TooltipDeleteMsg string + +// TooltipDelete deletes a tooltip. +func TooltipDelete(id string) tea.Cmd { + return func() tea.Msg { + return TooltipDeleteMsg(id) + } +} + +type TooltipClearMsg struct{} + +// TooltipClear clears all tooltips. +func TooltipClear() tea.Cmd { + return func() tea.Msg { + return TooltipClearMsg(struct{}{}) + } +}