diff --git a/bubbles/table/table.go b/bubbles/table/table.go new file mode 100644 index 0000000..ec6c50c --- /dev/null +++ b/bubbles/table/table.go @@ -0,0 +1,488 @@ +// Adjusted version of https://github.com/charmbracelet/bubbles/blob/master/table/table.go +// that provides a way to highlight rows +package table + +import ( + "slices" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/mattn/go-runewidth" +) + +// Model defines a state for the table widget. +type Model struct { + KeyMap KeyMap + Help help.Model + + cols []Column + rows []Row + highlighted []int + cursor int + focus bool + styles Styles + + viewport viewport.Model + start int + end int +} + +// Row represents one line in the table. +type Row []string + +// Column defines the table structure. +type Column struct { + Title string + Width int +} + +// KeyMap defines keybindings. It satisfies to the help.KeyMap interface, which +// is used to render the help menu. +type KeyMap struct { + LineUp key.Binding + LineDown key.Binding + PageUp key.Binding + PageDown key.Binding + HalfPageUp key.Binding + HalfPageDown key.Binding + GotoTop key.Binding + GotoBottom key.Binding +} + +// ShortHelp implements the KeyMap interface. +func (km KeyMap) ShortHelp() []key.Binding { + return []key.Binding{km.LineUp, km.LineDown} +} + +// FullHelp implements the KeyMap interface. +func (km KeyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {km.LineUp, km.LineDown, km.GotoTop, km.GotoBottom}, + {km.PageUp, km.PageDown, km.HalfPageUp, km.HalfPageDown}, + } +} + +// DefaultKeyMap returns a default set of keybindings. +func DefaultKeyMap() KeyMap { + const spacebar = " " + return KeyMap{ + LineUp: key.NewBinding( + key.WithKeys("up", "k"), + key.WithHelp("↑/k", "up"), + ), + LineDown: key.NewBinding( + key.WithKeys("down", "j"), + key.WithHelp("↓/j", "down"), + ), + PageUp: key.NewBinding( + key.WithKeys("b", "pgup"), + key.WithHelp("b/pgup", "page up"), + ), + PageDown: key.NewBinding( + key.WithKeys("f", "pgdown", spacebar), + key.WithHelp("f/pgdn", "page down"), + ), + HalfPageUp: key.NewBinding( + key.WithKeys("u", "ctrl+u"), + key.WithHelp("u", "½ page up"), + ), + HalfPageDown: key.NewBinding( + key.WithKeys("d", "ctrl+d"), + key.WithHelp("d", "½ page down"), + ), + GotoTop: key.NewBinding( + key.WithKeys("home", "g"), + key.WithHelp("g/home", "go to start"), + ), + GotoBottom: key.NewBinding( + key.WithKeys("end", "G"), + key.WithHelp("G/end", "go to end"), + ), + } +} + +// Styles contains style definitions for this list component. By default, these +// values are generated by DefaultStyles. +type Styles struct { + Header lipgloss.Style + Cell lipgloss.Style + Selected lipgloss.Style + Highlighted lipgloss.Style +} + +// DefaultStyles returns a set of default style definitions for this table. +func DefaultStyles() Styles { + return Styles{ + Highlighted: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212")), + Selected: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212")), + Header: lipgloss.NewStyle().Bold(true).Padding(0, 1), + Cell: lipgloss.NewStyle().Padding(0, 1), + } +} + +// SetStyles sets the table styles. +func (m *Model) SetStyles(s Styles) { + m.styles = s + m.UpdateViewport() +} + +// Option is used to set options in New. For example: +// +// table := New(WithColumns([]Column{{Title: "ID", Width: 10}})) +type Option func(*Model) + +// New creates a new model for the table widget. +func New(opts ...Option) Model { + m := Model{ + cursor: 0, + viewport: viewport.New(0, 20), + + KeyMap: DefaultKeyMap(), + Help: help.New(), + styles: DefaultStyles(), + } + + for _, opt := range opts { + opt(&m) + } + + m.UpdateViewport() + + return m +} + +// WithColumns sets the table columns (headers). +func WithColumns(cols []Column) Option { + return func(m *Model) { + m.cols = cols + } +} + +// WithRows sets the table rows (data). +func WithRows(rows []Row) Option { + return func(m *Model) { + m.rows = rows + } +} + +// WithHighlighted sets the table highlighted (data). +func WithHighlighted(highlighted []int) Option { + return func(m *Model) { + m.highlighted = highlighted + } +} + +// WithHeight sets the height of the table. +func WithHeight(h int) Option { + return func(m *Model) { + m.viewport.Height = h - lipgloss.Height(m.headersView()) + } +} + +// WithWidth sets the width of the table. +func WithWidth(w int) Option { + return func(m *Model) { + m.viewport.Width = w + } +} + +// WithFocused sets the focus state of the table. +func WithFocused(f bool) Option { + return func(m *Model) { + m.focus = f + } +} + +// WithStyles sets the table styles. +func WithStyles(s Styles) Option { + return func(m *Model) { + m.styles = s + } +} + +// WithKeyMap sets the key map. +func WithKeyMap(km KeyMap) Option { + return func(m *Model) { + m.KeyMap = km + } +} + +// Update is the Bubble Tea update loop. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + if !m.focus { + return m, nil + } + + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, m.KeyMap.LineUp): + m.MoveUp(1) + case key.Matches(msg, m.KeyMap.LineDown): + m.MoveDown(1) + case key.Matches(msg, m.KeyMap.PageUp): + m.MoveUp(m.viewport.Height) + case key.Matches(msg, m.KeyMap.PageDown): + m.MoveDown(m.viewport.Height) + case key.Matches(msg, m.KeyMap.HalfPageUp): + m.MoveUp(m.viewport.Height / 2) + case key.Matches(msg, m.KeyMap.HalfPageDown): + m.MoveDown(m.viewport.Height / 2) + case key.Matches(msg, m.KeyMap.LineDown): + m.MoveDown(1) + case key.Matches(msg, m.KeyMap.GotoTop): + m.GotoTop() + case key.Matches(msg, m.KeyMap.GotoBottom): + m.GotoBottom() + } + } + + return m, nil +} + +// Focused returns the focus state of the table. +func (m Model) Focused() bool { + return m.focus +} + +// Focus focuses the table, allowing the user to move around the rows and +// interact. +func (m *Model) Focus() { + m.focus = true + m.UpdateViewport() +} + +// Blur blurs the table, preventing selection or movement. +func (m *Model) Blur() { + m.focus = false + m.UpdateViewport() +} + +// View renders the component. +func (m Model) View() string { + return m.headersView() + "\n" + m.viewport.View() +} + +// HelpView is a helper method for rendering the help menu from the keymap. +// Note that this view is not rendered by default and you must call it +// manually in your application, where applicable. +func (m Model) HelpView() string { + return m.Help.View(m.KeyMap) +} + +// UpdateViewport updates the list content based on the previously defined +// columns and rows. +func (m *Model) UpdateViewport() { + renderedRows := make([]string, 0, len(m.rows)) + + // Render only rows from: m.cursor-m.viewport.Height to: m.cursor+m.viewport.Height + // Constant runtime, independent of number of rows in a table. + // Limits the number of renderedRows to a maximum of 2*m.viewport.Height + if m.cursor >= 0 { + m.start = clamp(m.cursor-m.viewport.Height, 0, m.cursor) + } else { + m.start = 0 + } + m.end = clamp(m.cursor+m.viewport.Height, m.cursor, len(m.rows)) + for i := m.start; i < m.end; i++ { + renderedRows = append(renderedRows, m.renderRow(i)) + } + + m.viewport.SetContent( + lipgloss.JoinVertical(lipgloss.Left, renderedRows...), + ) +} + +// SelectedRow returns the selected row. +// You can cast it to your own implementation. +func (m Model) SelectedRow() Row { + if m.cursor < 0 || m.cursor >= len(m.rows) { + return nil + } + + return m.rows[m.cursor] +} + +// Rows returns the current rows. +func (m Model) Rows() []Row { + return m.rows +} + +// Highlighted returns the current highlighted rows. +func (m Model) Highlighted() []int { + return m.highlighted +} + +// Columns returns the current columns. +func (m Model) Columns() []Column { + return m.cols +} + +// SetRows sets a new rows state. +func (m *Model) SetRows(r []Row) { + m.rows = r + m.UpdateViewport() +} + +// SetHighlighted sets a new highlighted state. +func (m *Model) SetHighlighted(h []int) { + m.highlighted = h + m.UpdateViewport() +} + +// SetColumns sets a new columns state. +func (m *Model) SetColumns(c []Column) { + m.cols = c + m.UpdateViewport() +} + +// SetWidth sets the width of the viewport of the table. +func (m *Model) SetWidth(w int) { + m.viewport.Width = w + m.UpdateViewport() +} + +// SetHeight sets the height of the viewport of the table. +func (m *Model) SetHeight(h int) { + m.viewport.Height = h - lipgloss.Height(m.headersView()) + m.UpdateViewport() +} + +// Height returns the viewport height of the table. +func (m Model) Height() int { + return m.viewport.Height +} + +// Width returns the viewport width of the table. +func (m Model) Width() int { + return m.viewport.Width +} + +// Cursor returns the index of the selected row. +func (m Model) Cursor() int { + return m.cursor +} + +// SetCursor sets the cursor position in the table. +func (m *Model) SetCursor(n int) { + m.cursor = clamp(n, 0, len(m.rows)-1) + m.UpdateViewport() +} + +// MoveUp moves the selection up by any number of rows. +// It can not go above the first row. +func (m *Model) MoveUp(n int) { + m.cursor = clamp(m.cursor-n, 0, len(m.rows)-1) + switch { + case m.start == 0: + m.viewport.SetYOffset(clamp(m.viewport.YOffset, 0, m.cursor)) + case m.start < m.viewport.Height: + m.viewport.YOffset = (clamp(clamp(m.viewport.YOffset+n, 0, m.cursor), 0, m.viewport.Height)) + case m.viewport.YOffset >= 1: + m.viewport.YOffset = clamp(m.viewport.YOffset+n, 1, m.viewport.Height) + } + m.UpdateViewport() +} + +// MoveDown moves the selection down by any number of rows. +// It can not go below the last row. +func (m *Model) MoveDown(n int) { + m.cursor = clamp(m.cursor+n, 0, len(m.rows)-1) + m.UpdateViewport() + + switch { + case m.end == len(m.rows) && m.viewport.YOffset > 0: + m.viewport.SetYOffset(clamp(m.viewport.YOffset-n, 1, m.viewport.Height)) + case m.cursor > (m.end-m.start)/2 && m.viewport.YOffset > 0: + m.viewport.SetYOffset(clamp(m.viewport.YOffset-n, 1, m.cursor)) + case m.viewport.YOffset > 1: + case m.cursor > m.viewport.YOffset+m.viewport.Height-1: + m.viewport.SetYOffset(clamp(m.viewport.YOffset+1, 0, 1)) + } +} + +// GotoTop moves the selection to the first row. +func (m *Model) GotoTop() { + m.MoveUp(m.cursor) +} + +// GotoBottom moves the selection to the last row. +func (m *Model) GotoBottom() { + m.MoveDown(len(m.rows)) +} + +// FromValues create the table rows from a simple string. It uses `\n` by +// default for getting all the rows and the given separator for the fields on +// each row. +func (m *Model) FromValues(value, separator string) { + rows := []Row{} + for _, line := range strings.Split(value, "\n") { + r := Row{} + for _, field := range strings.Split(line, separator) { + r = append(r, field) + } + rows = append(rows, r) + } + + m.SetRows(rows) +} + +func (m Model) headersView() string { + s := make([]string, 0, len(m.cols)) + for _, col := range m.cols { + if col.Width <= 0 { + continue + } + style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true) + renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…")) + s = append(s, m.styles.Header.Render(renderedCell)) + } + return lipgloss.JoinHorizontal(lipgloss.Top, s...) +} + +func (m *Model) renderRow(r int) string { + s := make([]string, 0, len(m.cols)) + for i, value := range m.rows[r] { + if m.cols[i].Width <= 0 { + continue + } + style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true) + renderedCell := m.styles.Cell.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…"))) + s = append(s, renderedCell) + } + + row := lipgloss.JoinHorizontal(lipgloss.Top, s...) + + if slices.Contains(m.highlighted, r) { + return m.styles.Highlighted.Render(row) + } + if r == m.cursor { + return m.styles.Selected.Render(row) + } + + return row +} + +func max(a, b int) int { + if a > b { + return a + } + + return b +} + +func min(a, b int) int { + if a < b { + return a + } + + return b +} + +func clamp(v, low, high int) int { + return min(max(v, low), high) +} diff --git a/model/compare_model.go b/model/compare_model.go new file mode 100644 index 0000000..3ad4f19 --- /dev/null +++ b/model/compare_model.go @@ -0,0 +1,125 @@ +package model + +import ( + "os" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "golang.org/x/term" +) + +var paddingStyle = lipgloss.NewStyle().Padding(0, 2) + +type CompareKeyMap struct { + Help key.Binding + Quit key.Binding +} + +// ShortHelp returns keybindings to be shown in the mini help view. It's part +// of the key.Map interface. +func (k CompareKeyMap) ShortHelp() []key.Binding { + return []key.Binding{k.Help, k.Quit} +} + +// FullHelp returns keybindings for the expanded help view. It's part of the +// key.Map interface. +func (k CompareKeyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Help, k.Quit}, + } +} + +var compareKeys = CompareKeyMap{ + Help: key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "toggle help"), + ), + Quit: key.NewBinding( + key.WithKeys("q", "ctrl+c"), + key.WithHelp("q", "quit"), + ), +} + +func NewCompareModel(mainModel *MainModel, refs ...string) CompareModel { + m := CompareModel{ + mainModel: mainModel, + UnitModels: make([]*Unit, 0), + } + + components := make([]string, 0) + for _, r := range refs { + um := NewUnitModel(r, mainModel) + m.UnitModels = append(m.UnitModels, um) + components = append(components, paddingStyle.Render(um.View())) + } + m.content = lipgloss.JoinHorizontal(lipgloss.Top, components...) + + width, height, _ := term.GetSize(int(os.Stdout.Fd())) + m.viewport = viewport.New(width, height) + m.viewport.SetContent(m.content) + m.ready = true + + return m +} + +type CompareModel struct { + UnitModels []*Unit + + viewport viewport.Model + mainModel *MainModel + ready bool + content string +} + +func (m CompareModel) Init() tea.Cmd { + return nil +} + +func (m CompareModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, compareKeys.Quit): + return m.mainModel.TableModel, cmd + } + if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" { + return m, tea.Quit + } + + case tea.WindowSizeMsg: + if !m.ready { + // Since this program is using the full size of the viewport we + // need to wait until we've received the window dimensions before + // we can initialize the viewport. The initial dimensions come in + // quickly, though asynchronously, which is why we wait for them + // here. + m.viewport = viewport.New(msg.Width, msg.Height) + m.viewport.HighPerformanceRendering = false + m.viewport.SetContent(m.content) + m.ready = true + } else { + m.viewport.Width = msg.Width + m.viewport.Height = msg.Height + } + + } + + // Handle keyboard and mouse events in the viewport + m.viewport, cmd = m.viewport.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} + +func (m CompareModel) View() string { + if !m.ready { + return "\n Initializing..." + } + return m.viewport.View() +} diff --git a/model/table_model.go b/model/table_model.go index a66ffe6..7bf2ce3 100644 --- a/model/table_model.go +++ b/model/table_model.go @@ -3,6 +3,7 @@ package model import ( "fmt" "regexp" + "slices" "sort" "strconv" "strings" @@ -10,10 +11,10 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/wezzle/bar-unit-info/bubbles/table" "github.com/wezzle/bar-unit-info/util" ) @@ -125,19 +126,20 @@ type TableKeyMap struct { FilterConfirm key.Binding FilterCancel key.Binding ToggleSort key.Binding + SelectRow key.Binding } // ShortHelp returns keybindings to be shown in the mini help view. It's part // of the key.Map interface. func (k TableKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.LineUp, k.LineDown, k.Left, k.Right, k.ToggleSort, k.Detail, k.Help, k.Quit} + return []key.Binding{k.LineUp, k.LineDown, k.Left, k.Right, k.ToggleSort, k.Detail, k.SelectRow, k.Help, k.Quit} } // FullHelp returns keybindings for the expanded help view. It's part of the // key.Map interface. func (k TableKeyMap) FullHelp() [][]key.Binding { return [][]key.Binding{ - {k.LineUp, k.LineDown, k.Left, k.Right, k.ToggleSort, k.Detail, k.Help, k.Quit}, + {k.LineUp, k.LineDown, k.Left, k.Right, k.ToggleSort, k.Detail, k.SelectRow, k.Help, k.Quit}, {k.GotoTop, k.GotoBottom, k.LineDown, k.PageDown, k.HalfPageUp, k.HalfPageDown}, } } @@ -181,8 +183,12 @@ var tableKeys = TableKeyMap{ key.WithHelp("", "cancel filter"), ), ToggleSort: key.NewBinding( + key.WithKeys("s"), + key.WithHelp("s", "toggle sort"), + ), + SelectRow: key.NewBinding( key.WithKeys(spacebar), - key.WithHelp("", "toggle sort"), + key.WithHelp("", "select row"), ), } @@ -285,6 +291,10 @@ func NewTableModel(mainModel *MainModel) Table { Foreground(lipgloss.Color("229")). Background(lipgloss.Color("57")). Bold(false) + s.Highlighted = s.Highlighted. + Foreground(lipgloss.Color("0")). + Background(lipgloss.Color("6")). + Bold(false) t.SetStyles(s) ti := textinput.New() @@ -330,6 +340,7 @@ type Table struct { columns []ColumnWithType columnFilters []string rows []table.Row + selectedRows []string } func (m *Table) FilterRows(cf []string) { @@ -379,6 +390,17 @@ func (m *Table) FilterRows(cf []string) { m.Table.SetRows(filteredRows) } +func (m *Table) SetHighlightedRows() { + h := make([]int, 0) + for i, r := range m.Table.Rows() { + ref := r[0] + if slices.Contains(m.selectedRows, ref) { + h = append(h, i) + } + } + m.Table.SetHighlighted(h) +} + func (m *Table) Init() tea.Cmd { return nil } @@ -388,6 +410,7 @@ func (m *Table) Update(msg tea.Msg) (tea.Model, tea.Cmd) { reverse := false var selectedCol *int var sortCol *int + preventPropagation := false if m.FilterMode { switch msg := msg.(type) { @@ -436,6 +459,15 @@ func (m *Table) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, tableKeys.Quit): return m, tea.Quit case key.Matches(msg, tableKeys.Detail): + selectedRef := m.Table.SelectedRow()[0] + selectedIsChosen := len(m.selectedRows) == 1 && m.selectedRows[0] == selectedRef + if len(m.selectedRows) > 0 && !selectedIsChosen { + if !slices.Contains(m.selectedRows, selectedRef) { + m.selectedRows = append(m.selectedRows) + m.SetHighlightedRows() + } + return NewCompareModel(m.mainModel, m.selectedRows...), cmd + } return NewUnitModel(m.Table.SelectedRow()[0], m.mainModel), cmd case key.Matches(msg, tableKeys.Left): s := max(m.SelectedCol-1, 0) @@ -445,6 +477,23 @@ func (m *Table) Update(msg tea.Msg) (tea.Model, tea.Cmd) { selectedCol = &s case key.Matches(msg, tableKeys.ToggleSort): sortCol = &m.SelectedCol + case key.Matches(msg, tableKeys.SelectRow): + selectedRows := make([]string, 0) + contains := false + ref := m.Table.SelectedRow()[0] + for _, r := range m.selectedRows { + if ref != r { + selectedRows = append(selectedRows, r) + } else { + contains = true + } + } + m.selectedRows = selectedRows + if !contains { + m.selectedRows = append(m.selectedRows, ref) + } + m.SetHighlightedRows() + preventPropagation = true } if sortCol != nil { @@ -487,6 +536,7 @@ func (m *Table) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return isLess }) m.Table.SetRows(rows) + m.SetHighlightedRows() } selectedColUpdate := selectedCol != nil && m.SelectedCol != *selectedCol @@ -503,7 +553,7 @@ func (m *Table) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.SelectedCol = *selectedCol } } - if sortCol != nil { + if preventPropagation { // Exit so we don't run table's keymap return m, cmd } diff --git a/model/unit_model.go b/model/unit_model.go index 6b5e893..8dde7d4 100644 --- a/model/unit_model.go +++ b/model/unit_model.go @@ -54,6 +54,7 @@ func NewUnitModel(ref util.UnitRef, mainModel *MainModel) *Unit { m.health = progress.New(progress.WithSolidFill("#49AE11"), progress.WithoutPercentage()) m.sightRange = progress.New(progress.WithSolidFill("#C6C8C9"), progress.WithoutPercentage()) m.speed = progress.New(progress.WithSolidFill("#1175AE"), progress.WithoutPercentage()) + m.buildpower = progress.New(progress.WithSolidFill("#6e17a3"), progress.WithoutPercentage()) return &m } @@ -74,6 +75,7 @@ type Unit struct { health progress.Model sightRange progress.Model speed progress.Model + buildpower progress.Model } func (m *Unit) Init() tea.Cmd { @@ -144,6 +146,10 @@ func (m *Unit) View() string { {"Speed", m.speed.ViewAs(m.PercentageWithBaseF(m.properties.Speed, 1.5)), strconv.Itoa(m.properties.SightDistance)}, } + if m.properties.Buildpower != nil { + stats = append(stats, []string{"Buildpower", m.buildpower.ViewAs(m.PercentageWithBase(*m.properties.Buildpower, 3)), strconv.Itoa(*m.properties.Buildpower)}) + } + maxLabelWidth := 0 maxValueWidth := 0 for _, stat := range stats { diff --git a/util/lua.go b/util/lua.go index 428b756..1f80888 100644 --- a/util/lua.go +++ b/util/lua.go @@ -158,6 +158,12 @@ func LoadUnitProperties(ref UnitRef) (*UnitProperties, error) { } speed, _ := strconv.ParseFloat(data.RawGetString("speed").String(), 64) + var workertime *int + if data.RawGetString("workertime").Type() != lua.LTNil { + workertimeVal, _ := strconv.Atoi(data.RawGetString("workertime").String()) + workertime = &workertimeVal + } + // Build option slice bo := data.RawGetString("buildoptions") var buildOptions []UnitRef @@ -221,6 +227,7 @@ func LoadUnitProperties(ref UnitRef) (*UnitProperties, error) { Health: health, SightDistance: sightdistance, Speed: speed, + Buildpower: workertime, CustomParams: &customParams, } unitPropertyCache[ref] = properties diff --git a/util/types.go b/util/types.go index 438f82a..364f9b2 100644 --- a/util/types.go +++ b/util/types.go @@ -194,6 +194,7 @@ type ( Health int SightDistance int Speed float64 + Buildpower *int WeaponDefs []WeaponDef CustomParams *CustomParams }