From 5d1416bb0772d70120751e0a7acba978521c8046 Mon Sep 17 00:00:00 2001 From: mvvasilev Date: Mon, 6 May 2024 20:43:35 +0300 Subject: [PATCH] Create player inventory menu --- {render => engine}/arbitrary_drawable.go | 2 +- {render => engine}/grid.go | 6 +- {render => engine}/layers.go | 2 +- {render => engine}/raw.go | 2 +- {render => engine}/rectangle.go | 2 +- {render => engine}/render_context.go | 2 +- {render => engine}/text.go | 2 +- {render => engine}/viewport.go | 2 +- game/game.go | 4 +- game/game_context.go | 6 +- game/model/inventory.go | 30 ++-- game/model/item.go | 117 --------------- game/model/item_type.go | 119 +++++++++++++++ game/model/player.go | 6 +- game/model/player_inventory.go | 72 ++++++++++ game/state/game_state.go | 4 +- game/state/inventory_screen_state.go | 117 ++------------- game/state/look_state.go | 4 +- game/state/main_menu_state.go | 12 +- game/state/pause_game_state.go | 10 +- game/state/playing_state.go | 10 +- game/state/quit_state.go | 6 +- {ui => game/ui}/border_button.go | 6 +- {ui => game/ui}/container.go | 0 {ui => game/ui}/label.go | 10 +- game/ui/menu/player_inventory_menu.go | 175 +++++++++++++++++++++++ {ui => game/ui}/simple_button.go | 12 +- {ui => game/ui}/ui.go | 4 +- {ui => game/ui}/window.go | 10 +- game/world/dungeon.go | 6 +- 30 files changed, 460 insertions(+), 300 deletions(-) rename {render => engine}/arbitrary_drawable.go (97%) rename {render => engine}/grid.go (98%) rename {render => engine}/layers.go (99%) rename {render => engine}/raw.go (98%) rename {render => engine}/rectangle.go (99%) rename {render => engine}/render_context.go (99%) rename {render => engine}/text.go (99%) rename {render => engine}/viewport.go (99%) create mode 100644 game/model/item_type.go create mode 100644 game/model/player_inventory.go rename {ui => game/ui}/border_button.go (93%) rename {ui => game/ui}/container.go (100%) rename {ui => game/ui}/label.go (79%) create mode 100644 game/ui/menu/player_inventory_menu.go rename {ui => game/ui}/simple_button.go (89%) rename {ui => game/ui}/ui.go (90%) rename {ui => game/ui}/window.go (84%) diff --git a/render/arbitrary_drawable.go b/engine/arbitrary_drawable.go similarity index 97% rename from render/arbitrary_drawable.go rename to engine/arbitrary_drawable.go index 9119b9c..3c37330 100644 --- a/render/arbitrary_drawable.go +++ b/engine/arbitrary_drawable.go @@ -1,4 +1,4 @@ -package render +package engine import ( "github.com/gdamore/tcell/v2/views" diff --git a/render/grid.go b/engine/grid.go similarity index 98% rename from render/grid.go rename to engine/grid.go index 4a50672..47bdae8 100644 --- a/render/grid.go +++ b/engine/grid.go @@ -1,4 +1,4 @@ -package render +package engine import ( "mvvasilev/last_light/util" @@ -119,6 +119,10 @@ func (g *Grid) Unhighlight() { g.isHighlighted = false } +func (g *Grid) Position() util.Position { + return g.position +} + // C###T###T###C // # # # # // # # # # diff --git a/render/layers.go b/engine/layers.go similarity index 99% rename from render/layers.go rename to engine/layers.go index 98a069f..e89a371 100644 --- a/render/layers.go +++ b/engine/layers.go @@ -1,4 +1,4 @@ -package render +package engine import ( "slices" diff --git a/render/raw.go b/engine/raw.go similarity index 98% rename from render/raw.go rename to engine/raw.go index e200682..0ea632e 100644 --- a/render/raw.go +++ b/engine/raw.go @@ -1,4 +1,4 @@ -package render +package engine import ( "mvvasilev/last_light/util" diff --git a/render/rectangle.go b/engine/rectangle.go similarity index 99% rename from render/rectangle.go rename to engine/rectangle.go index fb7753c..5b2d776 100644 --- a/render/rectangle.go +++ b/engine/rectangle.go @@ -1,4 +1,4 @@ -package render +package engine import ( "mvvasilev/last_light/util" diff --git a/render/render_context.go b/engine/render_context.go similarity index 99% rename from render/render_context.go rename to engine/render_context.go index d9c7acc..5879ab4 100644 --- a/render/render_context.go +++ b/engine/render_context.go @@ -1,4 +1,4 @@ -package render +package engine import ( "errors" diff --git a/render/text.go b/engine/text.go similarity index 99% rename from render/text.go rename to engine/text.go index 05e673e..8c133d6 100644 --- a/render/text.go +++ b/engine/text.go @@ -1,4 +1,4 @@ -package render +package engine import ( "mvvasilev/last_light/util" diff --git a/render/viewport.go b/engine/viewport.go similarity index 99% rename from render/viewport.go rename to engine/viewport.go index 2284f4e..6e994f2 100644 --- a/render/viewport.go +++ b/engine/viewport.go @@ -1,4 +1,4 @@ -package render +package engine import ( "mvvasilev/last_light/util" diff --git a/game/game.go b/game/game.go index 44a9a05..f663a07 100644 --- a/game/game.go +++ b/game/game.go @@ -1,8 +1,8 @@ package game import ( + "mvvasilev/last_light/engine" "mvvasilev/last_light/game/state" - "mvvasilev/last_light/render" "github.com/gdamore/tcell/v2" ) @@ -46,6 +46,6 @@ func (g *Game) Tick(dt int64) bool { return true } -func (g *Game) CollectDrawables() []render.Drawable { +func (g *Game) CollectDrawables() []engine.Drawable { return g.state.CollectDrawables() } diff --git a/game/game_context.go b/game/game_context.go index 2d04fe4..53dbb45 100644 --- a/game/game_context.go +++ b/game/game_context.go @@ -2,7 +2,7 @@ package game import ( "log" - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "os" "time" ) @@ -10,7 +10,7 @@ import ( const TICK_RATE int64 = 50 // tick every 50ms ( 20 ticks per second ) type GameContext struct { - renderContext *render.RenderContext + renderContext *engine.RenderContext game *Game } @@ -18,7 +18,7 @@ type GameContext struct { func CreateGameContext() *GameContext { gc := new(GameContext) - rc, err := render.CreateRenderContext() + rc, err := engine.CreateRenderContext() if err != nil { log.Fatalf("%~v", err) diff --git a/game/model/inventory.go b/game/model/inventory.go index f6a4161..32efdcb 100644 --- a/game/model/inventory.go +++ b/game/model/inventory.go @@ -2,13 +2,21 @@ package model import "mvvasilev/last_light/util" -type Inventory struct { +type Inventory interface { + Items() []*Item + Shape() util.Size + Push(item Item) bool + Drop(x, y int) *Item + ItemAt(x, y int) *Item +} + +type BasicInventory struct { contents []*Item shape util.Size } -func CreateInventory(shape util.Size) *Inventory { - inv := new(Inventory) +func CreateInventory(shape util.Size) *BasicInventory { + inv := new(BasicInventory) inv.contents = make([]*Item, 0, shape.Height()*shape.Width()) inv.shape = shape @@ -16,15 +24,15 @@ func CreateInventory(shape util.Size) *Inventory { return inv } -func (i *Inventory) Items() (items []*Item) { +func (i *BasicInventory) Items() (items []*Item) { return i.contents } -func (i *Inventory) Shape() util.Size { +func (i *BasicInventory) Shape() util.Size { return i.shape } -func (i *Inventory) Push(item Item) (success bool) { +func (i *BasicInventory) Push(item Item) (success bool) { if len(i.contents) == i.shape.Area() { return false } @@ -60,17 +68,21 @@ func (i *Inventory) Push(item Item) (success bool) { return true } -func (i *Inventory) Drop(x, y int) { +func (i *BasicInventory) Drop(x, y int) *Item { index := y*i.shape.Width() + x if index > len(i.contents)-1 { - return + return nil } + item := i.contents[index] + i.contents[index] = nil + + return item } -func (i *Inventory) ItemAt(x, y int) (item *Item) { +func (i *BasicInventory) ItemAt(x, y int) (item *Item) { index := y*i.shape.Width() + x if index > len(i.contents)-1 { diff --git a/game/model/item.go b/game/model/item.go index 0131889..ca66f5a 100644 --- a/game/model/item.go +++ b/game/model/item.go @@ -1,126 +1,9 @@ package model import ( - "math/rand" - "github.com/gdamore/tcell/v2" ) -type ItemType struct { - name string - description string - tileIcon rune - itemIcon string - maxStack int - - style tcell.Style -} - -func (it *ItemType) Name() string { - return it.name -} - -func (it *ItemType) Description() string { - return it.description -} - -func (it *ItemType) TileIcon() rune { - return it.tileIcon -} - -func (it *ItemType) Icon() string { - return it.itemIcon -} - -func (it *ItemType) Style() tcell.Style { - return it.style -} - -func (it *ItemType) MaxStack() int { - return it.maxStack -} - -func ItemTypeFish() *ItemType { - return &ItemType{ - name: "Fish", - description: "What's a fish doing down here?", - tileIcon: '>', - itemIcon: "»o>", - style: tcell.StyleDefault.Foreground(tcell.ColorDarkCyan), - maxStack: 16, - } -} - -func ItemTypeGold() *ItemType { - return &ItemType{ - name: "Gold", - description: "Not all those who wander are lost", - tileIcon: '¤', - itemIcon: " ¤ ", - style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod), - maxStack: 255, - } -} - -func ItemTypeArrow() *ItemType { - return &ItemType{ - name: "Arrow", - description: "Ammunition for a bow", - tileIcon: '-', - itemIcon: "»->", - style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod), - maxStack: 32, - } -} - -func ItemTypeBow() *ItemType { - return &ItemType{ - name: "Bow", - description: "To shoot arrows with", - tileIcon: ')', - itemIcon: " |)", - style: tcell.StyleDefault.Foreground(tcell.ColorBrown), - maxStack: 1, - } -} - -func ItemTypeLongsword() *ItemType { - return &ItemType{ - name: "Longsword", - description: "You know nothing.", - tileIcon: '/', - itemIcon: "╪══", - style: tcell.StyleDefault.Foreground(tcell.ColorSilver), - maxStack: 1, - } -} - -func ItemTypeKey() *ItemType { - return &ItemType{ - name: "Key", - description: "Indispensable for unlocking things", - tileIcon: '¬', - itemIcon: " o╖", - style: tcell.StyleDefault.Foreground(tcell.ColorDarkGoldenrod), - maxStack: 1, - } -} - -type ItemTypeGenTable struct { -} - -func GenerateItemType(genTable map[float32]*ItemType) *ItemType { - num := rand.Float32() - - for k, v := range genTable { - if num > k { - return v - } - } - - return nil -} - type Item struct { name string description string diff --git a/game/model/item_type.go b/game/model/item_type.go new file mode 100644 index 0000000..25bdd73 --- /dev/null +++ b/game/model/item_type.go @@ -0,0 +1,119 @@ +package model + +import ( + "math/rand" + + "github.com/gdamore/tcell/v2" +) + +type ItemType struct { + name string + description string + tileIcon rune + itemIcon string + maxStack int + + style tcell.Style +} + +func (it *ItemType) Name() string { + return it.name +} + +func (it *ItemType) Description() string { + return it.description +} + +func (it *ItemType) TileIcon() rune { + return it.tileIcon +} + +func (it *ItemType) Icon() string { + return it.itemIcon +} + +func (it *ItemType) Style() tcell.Style { + return it.style +} + +func (it *ItemType) MaxStack() int { + return it.maxStack +} + +func ItemTypeFish() *ItemType { + return &ItemType{ + name: "Fish", + description: "What's a fish doing down here?", + tileIcon: '>', + itemIcon: "»o>", + style: tcell.StyleDefault.Foreground(tcell.ColorDarkCyan), + maxStack: 16, + } +} + +func ItemTypeGold() *ItemType { + return &ItemType{ + name: "Gold", + description: "Not all those who wander are lost", + tileIcon: '¤', + itemIcon: " ¤ ", + style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod), + maxStack: 255, + } +} + +func ItemTypeArrow() *ItemType { + return &ItemType{ + name: "Arrow", + description: "Ammunition for a bow", + tileIcon: '-', + itemIcon: "»->", + style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod), + maxStack: 32, + } +} + +func ItemTypeBow() *ItemType { + return &ItemType{ + name: "Bow", + description: "To shoot arrows with", + tileIcon: ')', + itemIcon: " |)", + style: tcell.StyleDefault.Foreground(tcell.ColorBrown), + maxStack: 1, + } +} + +func ItemTypeLongsword() *ItemType { + return &ItemType{ + name: "Longsword", + description: "You know nothing.", + tileIcon: '/', + itemIcon: "╪══", + style: tcell.StyleDefault.Foreground(tcell.ColorSilver), + maxStack: 1, + } +} + +func ItemTypeKey() *ItemType { + return &ItemType{ + name: "Key", + description: "Indispensable for unlocking things", + tileIcon: '¬', + itemIcon: " o╖", + style: tcell.StyleDefault.Foreground(tcell.ColorDarkGoldenrod), + maxStack: 1, + } +} + +func GenerateItemType(genTable map[float32]*ItemType) *ItemType { + num := rand.Float32() + + for k, v := range genTable { + if num > k { + return v + } + } + + return nil +} diff --git a/game/model/player.go b/game/model/player.go index b534d77..bf017aa 100644 --- a/game/model/player.go +++ b/game/model/player.go @@ -11,7 +11,7 @@ type Player struct { id uuid.UUID position util.Position - inventory *Inventory + inventory *EquippedInventory } func CreatePlayer(x, y int) *Player { @@ -19,7 +19,7 @@ func CreatePlayer(x, y int) *Player { p.id = uuid.New() p.position = util.PositionAt(x, y) - p.inventory = CreateInventory(util.SizeOf(8, 4)) + p.inventory = CreatePlayerInventory() return p } @@ -48,7 +48,7 @@ func (p *Player) Transparent() bool { return false } -func (p *Player) Inventory() *Inventory { +func (p *Player) Inventory() *EquippedInventory { return p.inventory } diff --git a/game/model/player_inventory.go b/game/model/player_inventory.go new file mode 100644 index 0000000..1d933fa --- /dev/null +++ b/game/model/player_inventory.go @@ -0,0 +1,72 @@ +package model + +import "mvvasilev/last_light/util" + +type EquippedSlot int + +const ( + EquippedSlotOffhand EquippedSlot = iota + EquippedSlotDominantHand + EquippedSlotHead + EquippedSlotChestplate + EquippedSlotLeggings + EquippedSlotShoes +) + +type EquippedInventory struct { + offHand *Item + dominantHand *Item + + head *Item + chestplate *Item + leggings *Item + shoes *Item + + *BasicInventory +} + +func CreatePlayerInventory() *EquippedInventory { + return &EquippedInventory{ + BasicInventory: CreateInventory(util.SizeOf(8, 4)), + } +} + +func (ei *EquippedInventory) AtSlot(slot EquippedSlot) *Item { + switch slot { + case EquippedSlotOffhand: + return ei.offHand + case EquippedSlotDominantHand: + return ei.dominantHand + case EquippedSlotHead: + return ei.head + case EquippedSlotChestplate: + return ei.chestplate + case EquippedSlotLeggings: + return ei.leggings + case EquippedSlotShoes: + return ei.shoes + default: + return nil + } +} + +func (ei *EquippedInventory) Equip(item Item, slot EquippedSlot) *Item { + ref := &item + + switch slot { + case EquippedSlotOffhand: + ei.offHand = ref + case EquippedSlotDominantHand: + ei.dominantHand = ref + case EquippedSlotHead: + ei.head = ref + case EquippedSlotChestplate: + ei.chestplate = ref + case EquippedSlotLeggings: + ei.leggings = ref + case EquippedSlotShoes: + ei.shoes = ref + } + + return ref +} diff --git a/game/state/game_state.go b/game/state/game_state.go index 2a6f683..23e52ad 100644 --- a/game/state/game_state.go +++ b/game/state/game_state.go @@ -1,7 +1,7 @@ package state import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "github.com/gdamore/tcell/v2" ) @@ -9,7 +9,7 @@ import ( type GameState interface { OnInput(e *tcell.EventKey) OnTick(dt int64) GameState - CollectDrawables() []render.Drawable + CollectDrawables() []engine.Drawable } type PausableState interface { diff --git a/game/state/inventory_screen_state.go b/game/state/inventory_screen_state.go index 750117b..bba2d66 100644 --- a/game/state/inventory_screen_state.go +++ b/game/state/inventory_screen_state.go @@ -1,36 +1,24 @@ package state import ( - "fmt" + "mvvasilev/last_light/engine" "mvvasilev/last_light/game/model" - "mvvasilev/last_light/render" - "mvvasilev/last_light/ui" + "mvvasilev/last_light/game/ui/menu" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" - "github.com/gdamore/tcell/v2/views" ) type InventoryScreenState struct { prevState PausableState exitMenu bool - inventoryMenu *ui.UIWindow - armourLabel *ui.UILabel - armourGrid *render.Grid - leftHandLabel *ui.UILabel - leftHandBox render.Rectangle - rightHandLabel *ui.UILabel - rightHandBox render.Rectangle - inventoryGrid *render.Grid - playerItems *render.ArbitraryDrawable - selectedItem *render.ArbitraryDrawable - help *ui.UILabel + inventoryMenu *menu.PlayerInventoryMenu + selectedInventorySlot util.Position player *model.Player moveInventorySlotDirection model.Direction - selectedInventorySlot util.Position dropSelectedInventorySlot bool } @@ -39,88 +27,9 @@ func CreateInventoryScreenState(player *model.Player, prevState PausableState) * iss.prevState = prevState iss.player = player - iss.exitMenu = false iss.selectedInventorySlot = util.PositionAt(0, 0) - - iss.inventoryMenu = ui.CreateWindow(43, 0, 37, 24, "INVENTORY", tcell.StyleDefault) - - iss.armourLabel = ui.CreateSingleLineUILabel(58, 1, "ARMOUR", tcell.StyleDefault) - - iss.armourGrid = render.CreateGrid( - 53, 2, 3, 1, 4, 1, '┌', '─', '┬', '┐', '│', ' ', '│', '│', '├', '─', '┼', '┤', '└', '─', '┴', '┘', tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray), - ) - - iss.leftHandLabel = ui.CreateUILabel( - 46, 1, 5, 1, "OFF", tcell.StyleDefault, - ) - - iss.leftHandBox = render.CreateRectangle( - 45, 2, 5, 3, - '┌', '─', '┐', - '│', ' ', '│', - '└', '─', '┘', - false, true, - tcell.StyleDefault, - ) - - iss.rightHandLabel = ui.CreateUILabel( - 74, 1, 5, 1, "DOM", tcell.StyleDefault, - ) - - iss.rightHandBox = render.CreateRectangle( - 73, 2, 5, 3, - '┌', '─', '┐', - '│', ' ', '│', - '└', '─', '┘', - false, true, - tcell.StyleDefault, - ) - - iss.inventoryGrid = render.CreateGrid( - 45, 5, 3, 1, 8, 4, '┌', '─', '┬', '┐', '│', ' ', '│', '│', '├', '─', '┼', '┤', '└', '─', '┴', '┘', tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray), - ) - - iss.playerItems = render.CreateDrawingInstructions(func(v views.View) { - for y := range player.Inventory().Shape().Height() { - for x := range player.Inventory().Shape().Width() { - item := player.Inventory().ItemAt(x, y) - isHighlighted := x == iss.selectedInventorySlot.X() && y == iss.selectedInventorySlot.Y() - - if item == nil { - - if isHighlighted { - ui.CreateSingleLineUILabel(45+1+x*4, 5+1+y*2, " ", tcell.StyleDefault.Background(tcell.ColorDarkSlateGray)).Draw(v) - } - - continue - } - - style := item.Type().Style() - - if isHighlighted { - style = style.Background(tcell.ColorDarkSlateGray) - } - - ui.CreateSingleLineUILabel(45+1+x*4, 5+y*2, fmt.Sprintf("%03d", item.Quantity()), style).Draw(v) - ui.CreateSingleLineUILabel(45+1+x*4, 5+1+y*2, item.Type().Icon(), style).Draw(v) - } - } - }) - - iss.selectedItem = render.CreateDrawingInstructions(func(v views.View) { - ui.CreateWindow(45, 14, 33, 8, "ITEM", tcell.StyleDefault).Draw(v) - - item := player.Inventory().ItemAt(iss.selectedInventorySlot.XY()) - - if item == nil { - return - } - - ui.CreateSingleLineUILabel(46, 15, fmt.Sprintf("Name: %v", item.Name()), tcell.StyleDefault).Draw(v) - ui.CreateSingleLineUILabel(46, 16, fmt.Sprintf("Desc: %v", item.Description()), tcell.StyleDefault).Draw(v) - }) - - iss.help = ui.CreateSingleLineUILabel(45, 22, "hjkl - move, x - drop, e - equip", tcell.StyleDefault) + iss.exitMenu = false + iss.inventoryMenu = menu.CreatePlayerInventoryMenu(43, 0, player.Inventory(), tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray)) return iss } @@ -190,27 +99,17 @@ func (iss *InventoryScreenState) OnTick(dt int64) GameState { iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(+1, 0) } - iss.inventoryGrid.Highlight(iss.selectedInventorySlot) + iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY()) iss.moveInventorySlotDirection = model.DirectionNone } return iss } -func (iss *InventoryScreenState) CollectDrawables() []render.Drawable { +func (iss *InventoryScreenState) CollectDrawables() []engine.Drawable { drawables := append( iss.prevState.CollectDrawables(), iss.inventoryMenu, - iss.armourLabel, - iss.armourGrid, - iss.leftHandLabel, - iss.leftHandBox, - iss.rightHandLabel, - iss.rightHandBox, - iss.inventoryGrid, - iss.playerItems, - iss.selectedItem, - iss.help, ) return drawables diff --git a/game/state/look_state.go b/game/state/look_state.go index a091a26..f16a0ec 100644 --- a/game/state/look_state.go +++ b/game/state/look_state.go @@ -1,7 +1,7 @@ package state import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "github.com/gdamore/tcell/v2" ) @@ -17,6 +17,6 @@ func (ls *LookState) OnTick(dt int64) GameState { panic("not implemented") // TODO: Implement } -func (ls *LookState) CollectDrawables() []render.Drawable { +func (ls *LookState) CollectDrawables() []engine.Drawable { panic("not implemented") // TODO: Implement } diff --git a/game/state/main_menu_state.go b/game/state/main_menu_state.go index a78ed34..1597dd6 100644 --- a/game/state/main_menu_state.go +++ b/game/state/main_menu_state.go @@ -1,15 +1,15 @@ package state import ( - "mvvasilev/last_light/render" - "mvvasilev/last_light/ui" + "mvvasilev/last_light/engine" + "mvvasilev/last_light/game/ui" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" ) type MainMenuState struct { - menuTitle *render.Raw + menuTitle *engine.Raw buttons []*ui.UISimpleButton currButtonSelected int @@ -22,7 +22,7 @@ func NewMainMenuState() *MainMenuState { highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold) - state.menuTitle = render.CreateRawDrawable( + state.menuTitle = engine.CreateRawDrawable( 11, 1, tcell.StyleDefault.Attributes(tcell.AttrBold).Foreground(tcell.ColorYellow), " | | | _) | | ", " | _` | __| __| | | _` | __ \\ __|", @@ -77,8 +77,8 @@ func (mms *MainMenuState) OnTick(dt int64) GameState { return mms } -func (mms *MainMenuState) CollectDrawables() []render.Drawable { - arr := make([]render.Drawable, 0) +func (mms *MainMenuState) CollectDrawables() []engine.Drawable { + arr := make([]engine.Drawable, 0) arr = append(arr, mms.menuTitle) diff --git a/game/state/pause_game_state.go b/game/state/pause_game_state.go index 81db8d8..32a1717 100644 --- a/game/state/pause_game_state.go +++ b/game/state/pause_game_state.go @@ -1,8 +1,8 @@ package state import ( - "mvvasilev/last_light/render" - "mvvasilev/last_light/ui" + "mvvasilev/last_light/engine" + "mvvasilev/last_light/game/ui" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" @@ -26,7 +26,7 @@ func PauseGame(prevState PausableState) *PauseGameState { highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold) - s.pauseMenuWindow = ui.CreateWindow(int(render.TERMINAL_SIZE_WIDTH)/2-15, int(render.TERMINAL_SIZE_HEIGHT)/2-7, 30, 14, "PAUSED", tcell.StyleDefault) + s.pauseMenuWindow = ui.CreateWindow(int(engine.TERMINAL_SIZE_WIDTH)/2-15, int(engine.TERMINAL_SIZE_HEIGHT)/2-7, 30, 14, "PAUSED", tcell.StyleDefault) s.buttons = make([]*ui.UISimpleButton, 0) s.buttons = append( s.buttons, @@ -96,8 +96,8 @@ func (pg *PauseGameState) OnTick(dt int64) GameState { return pg } -func (pg *PauseGameState) CollectDrawables() []render.Drawable { - arr := make([]render.Drawable, 0) +func (pg *PauseGameState) CollectDrawables() []engine.Drawable { + arr := make([]engine.Drawable, 0) arr = append(arr, pg.prevState.CollectDrawables()...) diff --git a/game/state/playing_state.go b/game/state/playing_state.go index f2bec6b..319afbd 100644 --- a/game/state/playing_state.go +++ b/game/state/playing_state.go @@ -2,9 +2,9 @@ package state import ( "math/rand" + "mvvasilev/last_light/engine" "mvvasilev/last_light/game/model" "mvvasilev/last_light/game/world" - "mvvasilev/last_light/render" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" @@ -16,7 +16,7 @@ type PlayingState struct { entityMap *world.EntityMap level *world.MultilevelMap - viewport *render.Viewport + viewport *engine.Viewport movePlayerDirection model.Direction pauseGame bool @@ -51,7 +51,7 @@ func BeginPlayingState() *PlayingState { s.entityMap.AddEntity(s.player, '@', tcell.StyleDefault) - s.viewport = render.CreateViewport( + s.viewport = engine.CreateViewport( util.PositionAt(0, 0), dungeonLevel.PlayerSpawnPoint(), util.SizeOf(80, 24), @@ -207,8 +207,8 @@ func (ps *PlayingState) OnTick(dt int64) GameState { return ps } -func (ps *PlayingState) CollectDrawables() []render.Drawable { - return render.Multidraw(render.CreateDrawingInstructions(func(v views.View) { +func (ps *PlayingState) CollectDrawables() []engine.Drawable { + return engine.Multidraw(engine.CreateDrawingInstructions(func(v views.View) { ps.viewport.DrawFromProvider(v, func(x, y int) (rune, tcell.Style) { tile := ps.level.TileAt(x, y) diff --git a/game/state/quit_state.go b/game/state/quit_state.go index ef9ae0a..ed07b27 100644 --- a/game/state/quit_state.go +++ b/game/state/quit_state.go @@ -1,7 +1,7 @@ package state import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "github.com/gdamore/tcell/v2" ) @@ -17,6 +17,6 @@ func (q *QuitState) OnTick(dt int64) GameState { return q } -func (q *QuitState) CollectDrawables() []render.Drawable { - return render.Multidraw(nil) +func (q *QuitState) CollectDrawables() []engine.Drawable { + return engine.Multidraw(nil) } diff --git a/ui/border_button.go b/game/ui/border_button.go similarity index 93% rename from ui/border_button.go rename to game/ui/border_button.go index 74c7fcf..913c665 100644 --- a/ui/border_button.go +++ b/game/ui/border_button.go @@ -1,7 +1,7 @@ package ui import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" @@ -12,8 +12,8 @@ import ( type UIBorderedButton struct { id uuid.UUID - text render.Text - border render.Rectangle + text engine.Text + border engine.Rectangle isSelected bool diff --git a/ui/container.go b/game/ui/container.go similarity index 100% rename from ui/container.go rename to game/ui/container.go diff --git a/ui/label.go b/game/ui/label.go similarity index 79% rename from ui/label.go rename to game/ui/label.go index f0ad08d..fef57f8 100644 --- a/ui/label.go +++ b/game/ui/label.go @@ -1,7 +1,7 @@ package ui import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "mvvasilev/last_light/util" "unicode/utf8" @@ -12,14 +12,14 @@ import ( type UILabel struct { id uuid.UUID - text *render.Text + text *engine.Text } func CreateUILabel(x, y int, width, height int, content string, style tcell.Style) *UILabel { label := new(UILabel) label.id = uuid.New() - label.text = render.CreateText(x, y, width, height, content, style) + label.text = engine.CreateText(x, y, width, height, content, style) return label } @@ -28,7 +28,7 @@ func CreateSingleLineUILabel(x, y int, content string, style tcell.Style) *UILab label := new(UILabel) label.id = uuid.New() - label.text = render.CreateText(x, y, int(utf8.RuneCountInString(content)), 1, content, style) + label.text = engine.CreateText(x, y, int(utf8.RuneCountInString(content)), 1, content, style) return label } @@ -38,7 +38,7 @@ func (t *UILabel) UniqueId() uuid.UUID { } func (t *UILabel) MoveTo(x int, y int) { - t.text = render.CreateText(x, y, int(t.text.Size().Width()), int(t.Size().Height()), t.text.Content(), t.text.Style()) + t.text = engine.CreateText(x, y, int(t.text.Size().Width()), int(t.Size().Height()), t.text.Content(), t.text.Style()) } func (t *UILabel) Position() util.Position { diff --git a/game/ui/menu/player_inventory_menu.go b/game/ui/menu/player_inventory_menu.go new file mode 100644 index 0000000..8da4545 --- /dev/null +++ b/game/ui/menu/player_inventory_menu.go @@ -0,0 +1,175 @@ +package menu + +import ( + "fmt" + "mvvasilev/last_light/engine" + "mvvasilev/last_light/game/model" + "mvvasilev/last_light/game/ui" + "mvvasilev/last_light/util" + + "github.com/gdamore/tcell/v2" + "github.com/gdamore/tcell/v2/views" + "github.com/google/uuid" +) + +type PlayerInventoryMenu struct { + inventory *model.EquippedInventory + + inventoryMenu *ui.UIWindow + armourLabel *ui.UILabel + armourGrid *engine.Grid + leftHandLabel *ui.UILabel + leftHandBox engine.Rectangle + rightHandLabel *ui.UILabel + rightHandBox engine.Rectangle + inventoryGrid *engine.Grid + playerItems *engine.ArbitraryDrawable + selectedItem *engine.ArbitraryDrawable + help *ui.UILabel + + selectedInventorySlot util.Position +} + +func CreatePlayerInventoryMenu(x, y int, playerInventory *model.EquippedInventory, style tcell.Style, highlightStyle tcell.Style) *PlayerInventoryMenu { + menu := new(PlayerInventoryMenu) + + menu.inventory = playerInventory + + menu.inventoryMenu = ui.CreateWindow(x, y, 37, 24, "INVENTORY", style) + + menu.armourLabel = ui.CreateSingleLineUILabel(x+15, y+1, "ARMOUR", style) + + menu.armourGrid = engine.CreateGrid( + x+10, y+2, 3, 1, 4, 1, '┌', '─', '┬', '┐', '│', ' ', '│', '│', '├', '─', '┼', '┤', '└', '─', '┴', '┘', style, highlightStyle, //style.Background(tcell.ColorDarkSlateGray), + ) + + menu.leftHandLabel = ui.CreateUILabel( + x+3, y+1, 5, 1, "OFF", style, + ) + + menu.leftHandBox = engine.CreateRectangle( + x+2, y+2, 5, 3, + '┌', '─', '┐', + '│', ' ', '│', + '└', '─', '┘', + false, true, + style, + ) + + menu.rightHandLabel = ui.CreateUILabel( + x+31, y+1, 5, 1, "DOM", style, + ) + + menu.rightHandBox = engine.CreateRectangle( + x+30, y+2, 5, 3, + '┌', '─', '┐', + '│', ' ', '│', + '└', '─', '┘', + false, true, + style, + ) + + menu.inventoryGrid = engine.CreateGrid( + x+2, y+5, 3, 1, 8, 4, '┌', '─', '┬', '┐', '│', ' ', '│', '│', '├', '─', '┼', '┤', '└', '─', '┴', '┘', style, highlightStyle, + ) + + menu.playerItems = engine.CreateDrawingInstructions(func(v views.View) { + for y := range playerInventory.Shape().Height() { + for x := range playerInventory.Shape().Width() { + item := playerInventory.ItemAt(x, y) + isHighlighted := x == menu.selectedInventorySlot.X() && y == menu.selectedInventorySlot.Y() + + if item == nil { + + if isHighlighted { + ui.CreateSingleLineUILabel( + menu.inventoryGrid.Position().X()+1+x*4, + menu.inventoryGrid.Position().Y()+1+y*2, + " ", + highlightStyle, + ).Draw(v) + } + + continue + } + + style := item.Type().Style() + + if isHighlighted { + style = highlightStyle + } + + ui.CreateSingleLineUILabel( + menu.inventoryGrid.Position().X()+1+x*4, + menu.inventoryGrid.Position().Y()+y*2, + fmt.Sprintf("%03d", item.Quantity()), + style, + ).Draw(v) + + ui.CreateSingleLineUILabel( + menu.inventoryGrid.Position().X()+1+x*4, + menu.inventoryGrid.Position().Y()+1+y*2, + item.Type().Icon(), + style, + ).Draw(v) + } + } + }) + + menu.selectedItem = engine.CreateDrawingInstructions(func(v views.View) { + ui.CreateWindow(x+2, y+14, 33, 8, "ITEM", style).Draw(v) + + item := playerInventory.ItemAt(menu.selectedInventorySlot.XY()) + + if item == nil { + return + } + + ui.CreateSingleLineUILabel(x+3, y+15, fmt.Sprintf("Name: %v", item.Name()), style).Draw(v) + ui.CreateSingleLineUILabel(x+3, y+16, fmt.Sprintf("Desc: %v", item.Description()), style).Draw(v) + }) + + menu.help = ui.CreateSingleLineUILabel(x+2, y+22, "hjkl - move, x - drop, e - equip", style) + + return menu +} + +func (pim *PlayerInventoryMenu) MoveTo(x int, y int) { + +} + +func (pim *PlayerInventoryMenu) Position() util.Position { + return pim.inventoryMenu.Position() +} + +func (pim *PlayerInventoryMenu) Size() util.Size { + return pim.inventoryMenu.Size() +} + +func (pim *PlayerInventoryMenu) Input(e *tcell.EventKey) { + +} + +func (pim *PlayerInventoryMenu) UniqueId() uuid.UUID { + return pim.inventoryMenu.UniqueId() +} + +func (pim *PlayerInventoryMenu) SelectSlot(x, y int) { + pim.inventoryGrid.Unhighlight() + pim.selectedInventorySlot = util.PositionAt(x, y) + pim.inventoryGrid.Highlight(pim.selectedInventorySlot) +} + +func (pim *PlayerInventoryMenu) Draw(v views.View) { + pim.inventoryMenu.Draw(v) + pim.armourLabel.Draw(v) + pim.armourGrid.Draw(v) + pim.leftHandLabel.Draw(v) + pim.leftHandBox.Draw(v) + pim.rightHandLabel.Draw(v) + pim.rightHandBox.Draw(v) + pim.inventoryGrid.Draw(v) + pim.playerItems.Draw(v) + pim.selectedItem.Draw(v) + pim.help.Draw(v) +} diff --git a/ui/simple_button.go b/game/ui/simple_button.go similarity index 89% rename from ui/simple_button.go rename to game/ui/simple_button.go index 9f5502e..5539be7 100644 --- a/ui/simple_button.go +++ b/game/ui/simple_button.go @@ -1,7 +1,7 @@ package ui import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "mvvasilev/last_light/util" "strings" "unicode/utf8" @@ -14,7 +14,7 @@ import ( type UISimpleButton struct { id uuid.UUID isHighlighted bool - text *render.Text + text *engine.Text selectHandler func() unhighlightedStyle tcell.Style highlightedStyle tcell.Style @@ -24,7 +24,7 @@ func CreateSimpleButton(x, y int, text string, unhighlightedStyle, highlightedSt sb := new(UISimpleButton) sb.id = uuid.New() - sb.text = render.CreateText(x, y, int(utf8.RuneCountInString(text)), 1, text, unhighlightedStyle) + sb.text = engine.CreateText(x, y, int(utf8.RuneCountInString(text)), 1, text, unhighlightedStyle) sb.isHighlighted = false sb.selectHandler = onSelect sb.highlightedStyle = highlightedStyle @@ -50,7 +50,7 @@ func (sb *UISimpleButton) Highlight() { newContent := "[ " + sb.text.Content() + " ]" - sb.text = render.CreateText( + sb.text = engine.CreateText( int(sb.Position().X()-2), int(sb.Position().Y()), int(utf8.RuneCountInString(newContent)), 1, newContent, @@ -65,7 +65,7 @@ func (sb *UISimpleButton) Unhighlight() { content = strings.Trim(content, "[ ") contentLen := utf8.RuneCountInString(content) - sb.text = render.CreateText( + sb.text = engine.CreateText( int(sb.Position().X()+2), int(sb.Position().Y()), int(contentLen), 1, content, @@ -82,7 +82,7 @@ func (sb *UISimpleButton) UniqueId() uuid.UUID { } func (sb *UISimpleButton) MoveTo(x int, y int) { - sb.text = render.CreateText(x, y, int(utf8.RuneCountInString(sb.text.Content())), 1, sb.text.Content(), sb.highlightedStyle) + sb.text = engine.CreateText(x, y, int(utf8.RuneCountInString(sb.text.Content())), 1, sb.text.Content(), sb.highlightedStyle) } func (sb *UISimpleButton) Position() util.Position { diff --git a/ui/ui.go b/game/ui/ui.go similarity index 90% rename from ui/ui.go rename to game/ui/ui.go index e025921..b6e0c52 100644 --- a/ui/ui.go +++ b/game/ui/ui.go @@ -1,7 +1,7 @@ package ui import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" @@ -13,7 +13,7 @@ type UIElement interface { Size() util.Size Input(e *tcell.EventKey) - render.Drawable + engine.Drawable } type UIHighlightableElement interface { diff --git a/ui/window.go b/game/ui/window.go similarity index 84% rename from ui/window.go rename to game/ui/window.go index a20cab0..4c8b5c7 100644 --- a/ui/window.go +++ b/game/ui/window.go @@ -1,7 +1,7 @@ package ui import ( - "mvvasilev/last_light/render" + "mvvasilev/last_light/engine" "mvvasilev/last_light/util" "unicode/utf8" @@ -13,8 +13,8 @@ import ( type UIWindow struct { id uuid.UUID - title *render.Text - box render.Rectangle + title *engine.Text + box engine.Rectangle } func CreateWindow(x, y, width, height int, title string, style tcell.Style) *UIWindow { @@ -24,9 +24,9 @@ func CreateWindow(x, y, width, height int, title string, style tcell.Style) *UIW titlePos := (width / 2) - int(titleLen/2) - w.title = render.CreateText(x+titlePos, y, int(titleLen), 1, title, style) + w.title = engine.CreateText(x+titlePos, y, int(titleLen), 1, title, style) - w.box = render.CreateRectangle( + w.box = engine.CreateRectangle( x, y, width, height, '┌', '─', '┐', '│', ' ', '│', diff --git a/game/world/dungeon.go b/game/world/dungeon.go index 26418f7..b7ff090 100644 --- a/game/world/dungeon.go +++ b/game/world/dungeon.go @@ -1,15 +1,11 @@ package world -import "mvvasilev/last_light/game/model" - type dungeonLevel struct { - groundLevel Map + groundLevel *Map entityLevel *EntityMap itemLevel *Map } type Dungeon struct { - player *model.Player - levels []*dungeonLevel }