mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
Inventory screen
This commit is contained in:
parent
099155c186
commit
5864ab41ad
19 changed files with 1145 additions and 94 deletions
|
@ -29,3 +29,9 @@
|
||||||
- Underground City ( Caverns + Dungeon combo )
|
- Underground City ( Caverns + Dungeon combo )
|
||||||
- Objective: Pick up the Last Light and bring it to its Altar ( Altar of the Last Light )
|
- Objective: Pick up the Last Light and bring it to its Altar ( Altar of the Last Light )
|
||||||
- The light is always on level 9, but the Altar can be anywhere
|
- The light is always on level 9, but the Altar can be anywhere
|
||||||
|
|
||||||
|
Items:
|
||||||
|
- `»o>` - fish
|
||||||
|
- `╪══` - longsword
|
||||||
|
- `o─╖`
|
||||||
|
- `█▄`
|
BIN
__debug_bin2404317323
Executable file
BIN
__debug_bin2404317323
Executable file
Binary file not shown.
323
game/model/bsp_dungeon_level.go
Normal file
323
game/model/bsp_dungeon_level.go
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"mvvasilev/last_light/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type splitDirection bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
splitDirectionVertical splitDirection = true
|
||||||
|
splitDirectionHorizontal splitDirection = false
|
||||||
|
)
|
||||||
|
|
||||||
|
type bspNode struct {
|
||||||
|
origin util.Position
|
||||||
|
size util.Size
|
||||||
|
|
||||||
|
room Room
|
||||||
|
hasRoom bool
|
||||||
|
|
||||||
|
left *bspNode
|
||||||
|
right *bspNode
|
||||||
|
|
||||||
|
splitDir splitDirection
|
||||||
|
}
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
position util.Position
|
||||||
|
size util.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Room) Size() util.Size {
|
||||||
|
return r.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Room) Position() util.Position {
|
||||||
|
return r.position
|
||||||
|
}
|
||||||
|
|
||||||
|
type BSPDungeonLevel struct {
|
||||||
|
level *BasicMap
|
||||||
|
|
||||||
|
playerSpawnPoint util.Position
|
||||||
|
rooms []Room
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
|
||||||
|
root := new(bspNode)
|
||||||
|
|
||||||
|
root.origin = util.PositionAt(0, 0)
|
||||||
|
root.size = util.SizeOf(width, height)
|
||||||
|
|
||||||
|
split(root, numSplits)
|
||||||
|
|
||||||
|
tiles := make([][]Tile, height)
|
||||||
|
|
||||||
|
for h := range height {
|
||||||
|
tiles[h] = make([]Tile, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms := make([]Room, 0, 2^numSplits)
|
||||||
|
|
||||||
|
iterateBspLeaves(root, func(leaf *bspNode) {
|
||||||
|
x := util.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4)
|
||||||
|
y := util.RandInt(leaf.origin.Y(), leaf.origin.Y()+leaf.size.Height()/4)
|
||||||
|
w := util.RandInt(3, leaf.size.Width()-1)
|
||||||
|
h := util.RandInt(3, leaf.size.Height()-1)
|
||||||
|
|
||||||
|
if x+w >= width {
|
||||||
|
w = w - (x + w - width) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if y+h >= height {
|
||||||
|
h = h - (y + h - height) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
room := Room{
|
||||||
|
position: util.PositionAt(x, y),
|
||||||
|
size: util.SizeOf(w, h),
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms = append(rooms, room)
|
||||||
|
|
||||||
|
makeRoom(tiles, room)
|
||||||
|
|
||||||
|
leaf.room = room
|
||||||
|
leaf.hasRoom = true
|
||||||
|
})
|
||||||
|
|
||||||
|
iterateBspParents(root, func(parent *bspNode) {
|
||||||
|
roomLeft := findRoom(parent.left)
|
||||||
|
roomRight := findRoom(parent.right)
|
||||||
|
|
||||||
|
zCorridor(
|
||||||
|
tiles,
|
||||||
|
util.PositionAt(
|
||||||
|
roomLeft.position.X()+roomLeft.size.Width()/2,
|
||||||
|
roomLeft.position.Y()+roomLeft.size.Height()/2,
|
||||||
|
),
|
||||||
|
util.PositionAt(
|
||||||
|
roomRight.position.X()+roomRight.size.Width()/2,
|
||||||
|
roomRight.position.Y()+roomRight.size.Height()/2,
|
||||||
|
),
|
||||||
|
parent.splitDir,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
bsp := new(BSPDungeonLevel)
|
||||||
|
|
||||||
|
spawnRoom := findRoom(root.left)
|
||||||
|
|
||||||
|
bsp.rooms = rooms
|
||||||
|
bsp.level = CreateBasicMap(tiles)
|
||||||
|
bsp.playerSpawnPoint = util.PositionAt(
|
||||||
|
spawnRoom.position.X()+spawnRoom.size.Width()/2,
|
||||||
|
spawnRoom.position.Y()+spawnRoom.size.Height()/2,
|
||||||
|
)
|
||||||
|
|
||||||
|
return bsp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonLevel) PlayerSpawnPoint() util.Position {
|
||||||
|
return bsp.playerSpawnPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func findRoom(parent *bspNode) Room {
|
||||||
|
if parent.hasRoom {
|
||||||
|
return parent.room
|
||||||
|
}
|
||||||
|
|
||||||
|
if rand.Float32() > 0.5 {
|
||||||
|
return findRoom(parent.left)
|
||||||
|
} else {
|
||||||
|
return findRoom(parent.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func zCorridor(tiles [][]Tile, from util.Position, to util.Position, direction splitDirection) {
|
||||||
|
switch direction {
|
||||||
|
case splitDirectionHorizontal:
|
||||||
|
xMidPoint := (from.X() + to.X()) / 2
|
||||||
|
horizontalTunnel(tiles, from.X(), xMidPoint, from.Y())
|
||||||
|
horizontalTunnel(tiles, to.X(), xMidPoint, to.Y())
|
||||||
|
verticalTunnel(tiles, from.Y(), to.Y(), xMidPoint)
|
||||||
|
case splitDirectionVertical:
|
||||||
|
yMidPoint := (from.Y() + to.Y()) / 2
|
||||||
|
verticalTunnel(tiles, from.Y(), yMidPoint, from.X())
|
||||||
|
verticalTunnel(tiles, to.Y(), yMidPoint, to.X())
|
||||||
|
horizontalTunnel(tiles, from.X(), to.X(), yMidPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateBspParents(parent *bspNode, iter func(parent *bspNode)) {
|
||||||
|
if parent.left != nil && parent.right != nil {
|
||||||
|
iter(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.left != nil {
|
||||||
|
iterateBspParents(parent.left, iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.right != nil {
|
||||||
|
iterateBspParents(parent.right, iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateBspLeaves(parent *bspNode, iter func(leaf *bspNode)) {
|
||||||
|
if parent.left == nil && parent.right == nil {
|
||||||
|
iter(parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.left != nil {
|
||||||
|
iterateBspLeaves(parent.left, iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.right != nil {
|
||||||
|
iterateBspLeaves(parent.right, iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(parent *bspNode, numSplits int) {
|
||||||
|
if numSplits <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// split vertically
|
||||||
|
if parent.size.Width() > parent.size.Height() {
|
||||||
|
// New splits will be between 45% and 65% of the parent's width
|
||||||
|
leftSplitWidth := util.RandInt(int(float32(parent.size.Width())*0.45), int(float32(parent.size.Width())*0.65))
|
||||||
|
|
||||||
|
parent.splitDir = splitDirectionVertical
|
||||||
|
|
||||||
|
parent.left = new(bspNode)
|
||||||
|
parent.left.origin = parent.origin
|
||||||
|
parent.left.size = util.SizeOf(leftSplitWidth, parent.size.Height())
|
||||||
|
|
||||||
|
parent.right = new(bspNode)
|
||||||
|
parent.right.origin = parent.origin.WithOffset(leftSplitWidth, 0)
|
||||||
|
parent.right.size = util.SizeOf(parent.size.Width()-leftSplitWidth, parent.size.Height())
|
||||||
|
} else { // split horizontally
|
||||||
|
// New splits will be between 45% and 65% of the parent's height
|
||||||
|
leftSplitHeight := util.RandInt(int(float32(parent.size.Height())*0.45), int(float32(parent.size.Height())*0.65))
|
||||||
|
|
||||||
|
parent.splitDir = splitDirectionHorizontal
|
||||||
|
|
||||||
|
parent.left = new(bspNode)
|
||||||
|
parent.left.origin = parent.origin
|
||||||
|
parent.left.size = util.SizeOf(parent.size.Width(), leftSplitHeight)
|
||||||
|
|
||||||
|
parent.right = new(bspNode)
|
||||||
|
parent.right.origin = parent.origin.WithOffset(0, leftSplitHeight)
|
||||||
|
parent.right.size = util.SizeOf(parent.size.Width(), parent.size.Height()-leftSplitHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
split(parent.left, numSplits-1)
|
||||||
|
split(parent.right, numSplits-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func horizontalTunnel(tiles [][]Tile, x1, x2, y int) {
|
||||||
|
if x1 > x2 {
|
||||||
|
tx := x2
|
||||||
|
x2 = x1
|
||||||
|
x1 = tx
|
||||||
|
}
|
||||||
|
|
||||||
|
placeWallAtIfNotPassable(tiles, x1, y-1)
|
||||||
|
placeWallAtIfNotPassable(tiles, x1, y)
|
||||||
|
placeWallAtIfNotPassable(tiles, x1, y+1)
|
||||||
|
|
||||||
|
for x := x1; x <= x2; x++ {
|
||||||
|
if tiles[y][x] != nil && tiles[y][x].Passable() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
|
||||||
|
|
||||||
|
placeWallAtIfNotPassable(tiles, x, y-1)
|
||||||
|
placeWallAtIfNotPassable(tiles, x, y+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
placeWallAtIfNotPassable(tiles, x2, y-1)
|
||||||
|
placeWallAtIfNotPassable(tiles, x2, y)
|
||||||
|
placeWallAtIfNotPassable(tiles, x2, y+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verticalTunnel(tiles [][]Tile, y1, y2, x int) {
|
||||||
|
if y1 > y2 {
|
||||||
|
ty := y2
|
||||||
|
y2 = y1
|
||||||
|
y1 = ty
|
||||||
|
}
|
||||||
|
|
||||||
|
placeWallAtIfNotPassable(tiles, x-1, y1)
|
||||||
|
placeWallAtIfNotPassable(tiles, x, y1)
|
||||||
|
placeWallAtIfNotPassable(tiles, x+1, y1)
|
||||||
|
|
||||||
|
for y := y1; y <= y2; y++ {
|
||||||
|
if tiles[y][x] != nil && tiles[y][x].Passable() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
|
||||||
|
|
||||||
|
placeWallAtIfNotPassable(tiles, x-1, y)
|
||||||
|
placeWallAtIfNotPassable(tiles, x+1, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
placeWallAtIfNotPassable(tiles, x-1, y2)
|
||||||
|
placeWallAtIfNotPassable(tiles, x, y2)
|
||||||
|
placeWallAtIfNotPassable(tiles, x+1, y2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) {
|
||||||
|
if tiles[y][x] != nil && tiles[y][x].Passable() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles[y][x] = CreateStaticTile(x, y, TileTypeWall())
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRoom(tiles [][]Tile, room Room) {
|
||||||
|
width := room.size.Width()
|
||||||
|
height := room.size.Height()
|
||||||
|
x := room.position.X()
|
||||||
|
y := room.position.Y()
|
||||||
|
|
||||||
|
for w := x; w < x+width+1; w++ {
|
||||||
|
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
|
||||||
|
tiles[y+height][w] = CreateStaticTile(w, y+height, TileTypeWall())
|
||||||
|
}
|
||||||
|
|
||||||
|
for h := y; h < y+height+1; h++ {
|
||||||
|
tiles[h][x] = CreateStaticTile(x, h, TileTypeWall())
|
||||||
|
tiles[h][x+width] = CreateStaticTile(x+width, h, TileTypeWall())
|
||||||
|
}
|
||||||
|
|
||||||
|
for h := y + 1; h < y+height; h++ {
|
||||||
|
for w := x + 1; w < x+width; w++ {
|
||||||
|
tiles[h][w] = CreateStaticTile(w, h, TileTypeGround())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonLevel) Size() util.Size {
|
||||||
|
return bsp.level.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
||||||
|
bsp.level.SetTileAt(x, y, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonLevel) TileAt(x int, y int) Tile {
|
||||||
|
return bsp.level.TileAt(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonLevel) Tick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonLevel) Rooms() []Room {
|
||||||
|
return bsp.rooms
|
||||||
|
}
|
|
@ -1 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
type Dungeon struct {
|
||||||
|
player *Player
|
||||||
|
|
||||||
|
levels []Map
|
||||||
|
}
|
||||||
|
|
|
@ -3,43 +3,33 @@ package model
|
||||||
import "mvvasilev/last_light/util"
|
import "mvvasilev/last_light/util"
|
||||||
|
|
||||||
type EmptyDungeonLevel struct {
|
type EmptyDungeonLevel struct {
|
||||||
tiles [][]Tile
|
level *BasicMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonLevel {
|
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonLevel {
|
||||||
m := new(EmptyDungeonLevel)
|
m := new(EmptyDungeonLevel)
|
||||||
|
|
||||||
m.tiles = make([][]Tile, height)
|
tiles := make([][]Tile, height)
|
||||||
|
|
||||||
for h := range height {
|
for h := range height {
|
||||||
m.tiles[h] = make([]Tile, width)
|
tiles[h] = make([]Tile, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.level = CreateBasicMap(tiles)
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) Size() util.Size {
|
func (edl *EmptyDungeonLevel) Size() util.Size {
|
||||||
return util.SizeOf(len(edl.tiles[0]), len(edl.tiles))
|
return edl.level.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
func (edl *EmptyDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
||||||
if len(edl.tiles) <= y || len(edl.tiles[0]) <= x {
|
edl.level.SetTileAt(x, y, t)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
edl.tiles[y][x] = t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) TileAt(x int, y int) Tile {
|
func (edl *EmptyDungeonLevel) TileAt(x int, y int) Tile {
|
||||||
if y < 0 || y >= len(edl.tiles) {
|
return edl.level.TileAt(x, y)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if x < 0 || x >= len(edl.tiles[y]) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return edl.tiles[y][x]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) Tick() {
|
func (edl *EmptyDungeonLevel) Tick() {
|
||||||
|
|
|
@ -21,7 +21,7 @@ func CreateFlatGroundDungeonLevel(width, height int) *FlatGroundDungeonLevel {
|
||||||
|
|
||||||
for w := range width {
|
for w := range width {
|
||||||
if w == 0 || h == 0 || w >= width-1 || h >= height-1 {
|
if w == 0 || h == 0 || w >= width-1 || h >= height-1 {
|
||||||
level.tiles[h][w] = CreateStaticTile(w, h, Rock())
|
level.tiles[h][w] = CreateStaticTile(w, h, TileTypeRock())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,16 +35,16 @@ func CreateFlatGroundDungeonLevel(width, height int) *FlatGroundDungeonLevel {
|
||||||
func genRandomGroundTile(width, height int) Tile {
|
func genRandomGroundTile(width, height int) Tile {
|
||||||
switch rand.Intn(2) {
|
switch rand.Intn(2) {
|
||||||
case 0:
|
case 0:
|
||||||
return CreateStaticTile(width, height, Ground())
|
return CreateStaticTile(width, height, TileTypeGround())
|
||||||
case 1:
|
case 1:
|
||||||
return CreateStaticTile(width, height, Grass())
|
return CreateStaticTile(width, height, TileTypeGrass())
|
||||||
default:
|
default:
|
||||||
return CreateStaticTile(width, height, Ground())
|
return CreateStaticTile(width, height, TileTypeGround())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) Size() util.Size {
|
func (edl *FlatGroundDungeonLevel) Size() util.Size {
|
||||||
return util.SizeOfInt(len(edl.tiles[0]), len(edl.tiles))
|
return util.SizeOf(len(edl.tiles[0]), len(edl.tiles))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
func (edl *FlatGroundDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
||||||
|
|
81
game/model/inventory.go
Normal file
81
game/model/inventory.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "mvvasilev/last_light/util"
|
||||||
|
|
||||||
|
type Inventory struct {
|
||||||
|
contents []*Item
|
||||||
|
shape util.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateInventory(shape util.Size) *Inventory {
|
||||||
|
inv := new(Inventory)
|
||||||
|
|
||||||
|
inv.contents = make([]*Item, 0, shape.Height()*shape.Width())
|
||||||
|
inv.shape = shape
|
||||||
|
|
||||||
|
return inv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inventory) Items() (items []*Item) {
|
||||||
|
return i.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inventory) Shape() util.Size {
|
||||||
|
return i.shape
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inventory) Push(item Item) (success bool) {
|
||||||
|
if len(i.contents) == i.shape.Area() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
itemType := item.Type()
|
||||||
|
|
||||||
|
// Try to first find a matching item with capacity
|
||||||
|
for index, existingItem := range i.contents {
|
||||||
|
if existingItem != nil && existingItem.itemType == itemType {
|
||||||
|
if existingItem.Quantity()+1 > existingItem.Type().MaxStack() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
it := CreateItem(itemType, existingItem.Quantity()+1)
|
||||||
|
i.contents[index] = &it
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, try to find an intermediate empty slot to fit this item into
|
||||||
|
for index, existingItem := range i.contents {
|
||||||
|
if existingItem == nil {
|
||||||
|
i.contents[index] = &item
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, just append the new item at the end
|
||||||
|
i.contents = append(i.contents, &item)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inventory) Drop(x, y int) {
|
||||||
|
index := y*i.shape.Width() + x
|
||||||
|
|
||||||
|
if index > len(i.contents)-1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.contents[index] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inventory) ItemAt(x, y int) (item *Item) {
|
||||||
|
index := y*i.shape.Width() + x
|
||||||
|
|
||||||
|
if index > len(i.contents)-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.contents[index]
|
||||||
|
}
|
185
game/model/item.go
Normal file
185
game/model/item.go
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
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
|
||||||
|
itemType *ItemType
|
||||||
|
quantity int
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmptyItem() Item {
|
||||||
|
return Item{
|
||||||
|
itemType: &ItemType{
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
tileIcon: ' ',
|
||||||
|
itemIcon: " ",
|
||||||
|
style: tcell.StyleDefault,
|
||||||
|
maxStack: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateItem(itemType *ItemType, quantity int) Item {
|
||||||
|
return Item{
|
||||||
|
itemType: itemType,
|
||||||
|
quantity: quantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Item) WithName(name string) Item {
|
||||||
|
i.name = name
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Item) Name() string {
|
||||||
|
if i.name == "" {
|
||||||
|
return i.itemType.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Item) Description() string {
|
||||||
|
if i.description == "" {
|
||||||
|
return i.itemType.description
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Item) WithDescription(description string) Item {
|
||||||
|
i.description = description
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Item) Type() *ItemType {
|
||||||
|
return i.itemType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Item) Quantity() int {
|
||||||
|
return i.quantity
|
||||||
|
}
|
|
@ -10,3 +10,52 @@ type Map interface {
|
||||||
TileAt(x, y int) Tile
|
TileAt(x, y int) Tile
|
||||||
Tick()
|
Tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BasicMap struct {
|
||||||
|
tiles [][]Tile
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBasicMap(tiles [][]Tile) *BasicMap {
|
||||||
|
bm := new(BasicMap)
|
||||||
|
|
||||||
|
bm.tiles = tiles
|
||||||
|
|
||||||
|
return bm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BasicMap) Tick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BasicMap) Size() util.Size {
|
||||||
|
return util.SizeOf(len(bm.tiles[0]), len(bm.tiles))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BasicMap) SetTileAt(x int, y int, t Tile) {
|
||||||
|
if len(bm.tiles) <= y || len(bm.tiles[0]) <= x {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if x < 0 || y < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bm.tiles[y][x] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *BasicMap) TileAt(x int, y int) Tile {
|
||||||
|
if x < 0 || y < 0 {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
if x >= bm.Size().Width() || y >= bm.Size().Height() {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
tile := bm.tiles[y][x]
|
||||||
|
|
||||||
|
if tile == nil {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile
|
||||||
|
}
|
||||||
|
|
|
@ -42,16 +42,64 @@ func (mm *MultilevelMap) SetTileAtHeight(x, y, height int, t Tile) {
|
||||||
mm.layers[height].SetTileAt(x, y, t)
|
mm.layers[height].SetTileAt(x, y, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mm *MultilevelMap) TileAt(x int, y int) Tile {
|
func (mm *MultilevelMap) CollectTilesAt(x, y int, filter func(t Tile) bool) []Tile {
|
||||||
|
tiles := make([]Tile, len(mm.layers))
|
||||||
|
|
||||||
|
if x < 0 || y < 0 {
|
||||||
|
return tiles
|
||||||
|
}
|
||||||
|
|
||||||
|
if x >= mm.Size().Width() || y >= mm.Size().Height() {
|
||||||
|
return tiles
|
||||||
|
}
|
||||||
|
|
||||||
for i := len(mm.layers) - 1; i >= 0; i-- {
|
for i := len(mm.layers) - 1; i >= 0; i-- {
|
||||||
tile := mm.layers[i].TileAt(x, y)
|
tile := mm.layers[i].TileAt(x, y)
|
||||||
|
|
||||||
if tile != nil {
|
if tile != nil && !tile.Transparent() && filter(tile) {
|
||||||
return tile
|
tiles = append(tiles, tile)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return tiles
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *MultilevelMap) TileAt(x int, y int) Tile {
|
||||||
|
if x < 0 || y < 0 {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
if x >= mm.Size().Width() || y >= mm.Size().Height() {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(mm.layers) - 1; i >= 0; i-- {
|
||||||
|
tile := mm.layers[i].TileAt(x, y)
|
||||||
|
|
||||||
|
if tile != nil && !tile.Transparent() {
|
||||||
|
return tile
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *MultilevelMap) TileAtHeight(x, y, height int) Tile {
|
||||||
|
if x < 0 || y < 0 {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
if x >= mm.Size().Width() || y >= mm.Size().Height() {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
if height > len(mm.layers)-1 {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
return mm.layers[height].TileAt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mm *MultilevelMap) Tick() {
|
func (mm *MultilevelMap) Tick() {
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
type Player struct {
|
type Player struct {
|
||||||
id uuid.UUID
|
id uuid.UUID
|
||||||
position util.Position
|
position util.Position
|
||||||
|
|
||||||
|
inventory *Inventory
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreatePlayer(x, y int) *Player {
|
func CreatePlayer(x, y int) *Player {
|
||||||
|
@ -17,6 +19,7 @@ func CreatePlayer(x, y int) *Player {
|
||||||
|
|
||||||
p.id = uuid.New()
|
p.id = uuid.New()
|
||||||
p.position = util.PositionAt(x, y)
|
p.position = util.PositionAt(x, y)
|
||||||
|
p.inventory = CreateInventory(util.SizeOf(8, 4))
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
@ -33,14 +36,22 @@ func (p *Player) Move(dir Direction) {
|
||||||
p.position = p.Position().WithOffset(MovementDirectionOffset(dir))
|
p.position = p.Position().WithOffset(MovementDirectionOffset(dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Presentation() rune {
|
func (p *Player) Presentation() (rune, tcell.Style) {
|
||||||
return '@'
|
return '@', tcell.StyleDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Passable() bool {
|
func (p *Player) Passable() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Player) Transparent() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) Inventory() *Inventory {
|
||||||
|
return p.inventory
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Player) Input(e *tcell.EventKey) {
|
func (p *Player) Input(e *tcell.EventKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import "mvvasilev/last_light/util"
|
import (
|
||||||
|
"mvvasilev/last_light/util"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
type Material uint
|
type Material uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MaterialGround Material = iota
|
MaterialGround Material = iota
|
||||||
MaterialRock
|
MaterialRock
|
||||||
|
MaterialWall
|
||||||
MaterialGrass
|
MaterialGrass
|
||||||
MaterialVoid
|
MaterialVoid
|
||||||
)
|
)
|
||||||
|
@ -15,44 +20,65 @@ type TileType struct {
|
||||||
Material Material
|
Material Material
|
||||||
Passable bool
|
Passable bool
|
||||||
Presentation rune
|
Presentation rune
|
||||||
|
Transparent bool
|
||||||
|
Style tcell.Style
|
||||||
}
|
}
|
||||||
|
|
||||||
func Ground() TileType {
|
func TileTypeGround() TileType {
|
||||||
return TileType{
|
return TileType{
|
||||||
Material: MaterialGround,
|
Material: MaterialGround,
|
||||||
Passable: true,
|
Passable: true,
|
||||||
Presentation: '.',
|
Presentation: '.',
|
||||||
|
Transparent: false,
|
||||||
|
Style: tcell.StyleDefault,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Rock() TileType {
|
func TileTypeRock() TileType {
|
||||||
return TileType{
|
return TileType{
|
||||||
Material: MaterialRock,
|
Material: MaterialRock,
|
||||||
Passable: false,
|
Passable: false,
|
||||||
Presentation: '█',
|
Presentation: '█',
|
||||||
|
Transparent: false,
|
||||||
|
Style: tcell.StyleDefault,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Grass() TileType {
|
func TileTypeGrass() TileType {
|
||||||
return TileType{
|
return TileType{
|
||||||
Material: MaterialGrass,
|
Material: MaterialGrass,
|
||||||
Passable: true,
|
Passable: true,
|
||||||
Presentation: ',',
|
Presentation: ',',
|
||||||
|
Transparent: false,
|
||||||
|
Style: tcell.StyleDefault,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Void() TileType {
|
func TileTypeVoid() TileType {
|
||||||
return TileType{
|
return TileType{
|
||||||
Material: MaterialVoid,
|
Material: MaterialVoid,
|
||||||
Passable: false,
|
Passable: false,
|
||||||
Presentation: ' ',
|
Presentation: ' ',
|
||||||
|
Transparent: true,
|
||||||
|
Style: tcell.StyleDefault,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TileTypeWall() TileType {
|
||||||
|
return TileType{
|
||||||
|
Material: MaterialWall,
|
||||||
|
Passable: false,
|
||||||
|
Presentation: '#',
|
||||||
|
Transparent: false,
|
||||||
|
Style: tcell.StyleDefault.Background(tcell.ColorGray),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tile interface {
|
type Tile interface {
|
||||||
Position() util.Position
|
Position() util.Position
|
||||||
Presentation() rune
|
Presentation() (rune, tcell.Style)
|
||||||
Passable() bool
|
Passable() bool
|
||||||
|
Transparent() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaticTile struct {
|
type StaticTile struct {
|
||||||
|
@ -73,14 +99,58 @@ func (st *StaticTile) Position() util.Position {
|
||||||
return st.position
|
return st.position
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StaticTile) Presentation() rune {
|
func (st *StaticTile) Presentation() (rune, tcell.Style) {
|
||||||
return st.t.Presentation
|
return st.t.Presentation, st.t.Style
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StaticTile) Passable() bool {
|
func (st *StaticTile) Passable() bool {
|
||||||
return st.t.Passable
|
return st.t.Passable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *StaticTile) Transparent() bool {
|
||||||
|
return st.t.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
func (st *StaticTile) Type() TileType {
|
func (st *StaticTile) Type() TileType {
|
||||||
return st.t
|
return st.t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ItemTile struct {
|
||||||
|
position util.Position
|
||||||
|
itemType *ItemType
|
||||||
|
quantity int
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *ItemTile {
|
||||||
|
it := new(ItemTile)
|
||||||
|
|
||||||
|
it.position = position
|
||||||
|
it.itemType = itemType
|
||||||
|
it.quantity = quantity
|
||||||
|
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ItemTile) Type() *ItemType {
|
||||||
|
return it.itemType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ItemTile) Quantity() int {
|
||||||
|
return it.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ItemTile) Position() util.Position {
|
||||||
|
return it.position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ItemTile) Presentation() (rune, tcell.Style) {
|
||||||
|
return it.itemType.tileIcon, it.itemType.style
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ItemTile) Passable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ItemTile) Transparent() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -1,30 +1,126 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/render"
|
"mvvasilev/last_light/render"
|
||||||
"mvvasilev/last_light/ui"
|
"mvvasilev/last_light/ui"
|
||||||
|
"mvvasilev/last_light/util"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/gdamore/tcell/v2/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InventoryScreenState struct {
|
type InventoryScreenState struct {
|
||||||
prevState GameState
|
prevState PausableState
|
||||||
exitMenu bool
|
exitMenu bool
|
||||||
|
|
||||||
inventoryMenu *ui.UIWindow
|
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
|
||||||
|
|
||||||
player *model.Player
|
player *model.Player
|
||||||
|
|
||||||
|
moveInventorySlotDirection model.Direction
|
||||||
|
selectedInventorySlot util.Position
|
||||||
|
dropSelectedInventorySlot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateInventoryScreenState(player *model.Player, prevState GameState) *InventoryScreenState {
|
func CreateInventoryScreenState(player *model.Player, prevState PausableState) *InventoryScreenState {
|
||||||
iss := new(InventoryScreenState)
|
iss := new(InventoryScreenState)
|
||||||
|
|
||||||
iss.prevState = prevState
|
iss.prevState = prevState
|
||||||
iss.player = player
|
iss.player = player
|
||||||
iss.exitMenu = false
|
iss.exitMenu = false
|
||||||
|
iss.selectedInventorySlot = util.PositionAt(0, 0)
|
||||||
|
|
||||||
iss.inventoryMenu = ui.CreateWindow(40, 0, 40, 24, "INVENTORY", tcell.StyleDefault)
|
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)
|
||||||
|
|
||||||
return iss
|
return iss
|
||||||
}
|
}
|
||||||
|
@ -33,19 +129,89 @@ func (iss *InventoryScreenState) OnInput(e *tcell.EventKey) {
|
||||||
if e.Key() == tcell.KeyEsc || (e.Key() == tcell.KeyRune && e.Rune() == 'i') {
|
if e.Key() == tcell.KeyEsc || (e.Key() == tcell.KeyRune && e.Rune() == 'i') {
|
||||||
iss.exitMenu = true
|
iss.exitMenu = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Key() == tcell.KeyRune && e.Rune() == 'x' {
|
||||||
|
iss.dropSelectedInventorySlot = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Key() != tcell.KeyRune {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.Rune() {
|
||||||
|
case 'k':
|
||||||
|
iss.moveInventorySlotDirection = model.DirectionUp
|
||||||
|
case 'j':
|
||||||
|
iss.moveInventorySlotDirection = model.DirectionDown
|
||||||
|
case 'h':
|
||||||
|
iss.moveInventorySlotDirection = model.DirectionLeft
|
||||||
|
case 'l':
|
||||||
|
iss.moveInventorySlotDirection = model.DirectionRight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iss *InventoryScreenState) OnTick(dt int64) GameState {
|
func (iss *InventoryScreenState) OnTick(dt int64) GameState {
|
||||||
if iss.exitMenu {
|
if iss.exitMenu {
|
||||||
|
iss.prevState.Unpause()
|
||||||
return iss.prevState
|
return iss.prevState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if iss.dropSelectedInventorySlot {
|
||||||
|
iss.player.Inventory().Drop(iss.selectedInventorySlot.XY())
|
||||||
|
iss.dropSelectedInventorySlot = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if iss.moveInventorySlotDirection != model.DirectionNone {
|
||||||
|
|
||||||
|
switch iss.moveInventorySlotDirection {
|
||||||
|
case model.DirectionUp:
|
||||||
|
if iss.selectedInventorySlot.Y() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, -1)
|
||||||
|
case model.DirectionDown:
|
||||||
|
if iss.selectedInventorySlot.Y() == iss.player.Inventory().Shape().Height()-1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, +1)
|
||||||
|
case model.DirectionLeft:
|
||||||
|
if iss.selectedInventorySlot.X() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(-1, 0)
|
||||||
|
case model.DirectionRight:
|
||||||
|
if iss.selectedInventorySlot.X() == iss.player.Inventory().Shape().Width()-1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(+1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.inventoryGrid.Highlight(iss.selectedInventorySlot)
|
||||||
|
iss.moveInventorySlotDirection = model.DirectionNone
|
||||||
|
}
|
||||||
|
|
||||||
return iss
|
return iss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iss *InventoryScreenState) CollectDrawables() []render.Drawable {
|
func (iss *InventoryScreenState) CollectDrawables() []render.Drawable {
|
||||||
return append(
|
drawables := append(
|
||||||
iss.prevState.CollectDrawables(),
|
iss.prevState.CollectDrawables(),
|
||||||
iss.inventoryMenu,
|
iss.inventoryMenu,
|
||||||
|
iss.armourLabel,
|
||||||
|
iss.armourGrid,
|
||||||
|
iss.leftHandLabel,
|
||||||
|
iss.leftHandBox,
|
||||||
|
iss.rightHandLabel,
|
||||||
|
iss.rightHandBox,
|
||||||
|
iss.inventoryGrid,
|
||||||
|
iss.playerItems,
|
||||||
|
iss.selectedItem,
|
||||||
|
iss.help,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return drawables
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/render"
|
"mvvasilev/last_light/render"
|
||||||
"mvvasilev/last_light/util"
|
"mvvasilev/last_light/util"
|
||||||
|
@ -18,6 +19,7 @@ type PlayingState struct {
|
||||||
movePlayerDirection model.Direction
|
movePlayerDirection model.Direction
|
||||||
pauseGame bool
|
pauseGame bool
|
||||||
openInventory bool
|
openInventory bool
|
||||||
|
pickUpUnderPlayer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeginPlayingState() *PlayingState {
|
func BeginPlayingState() *PlayingState {
|
||||||
|
@ -25,18 +27,29 @@ func BeginPlayingState() *PlayingState {
|
||||||
|
|
||||||
mapSize := util.SizeOf(128, 128)
|
mapSize := util.SizeOf(128, 128)
|
||||||
|
|
||||||
s.player = model.CreatePlayer(40, 12)
|
dungeonLevel := model.CreateBSPDungeonLevel(mapSize.Width(), mapSize.Height(), 4)
|
||||||
|
|
||||||
|
itemTiles := spawnItems(dungeonLevel)
|
||||||
|
|
||||||
|
itemLevel := model.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height())
|
||||||
|
|
||||||
|
for _, it := range itemTiles {
|
||||||
|
itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY())
|
||||||
|
|
||||||
s.level = model.CreateMultilevelMap(
|
s.level = model.CreateMultilevelMap(
|
||||||
model.CreateFlatGroundDungeonLevel(mapSize.WH()),
|
dungeonLevel,
|
||||||
|
itemLevel,
|
||||||
model.CreateEmptyDungeonLevel(mapSize.WH()),
|
model.CreateEmptyDungeonLevel(mapSize.WH()),
|
||||||
)
|
)
|
||||||
|
|
||||||
s.level.SetTileAtHeight(40, 12, 1, s.player)
|
s.level.SetTileAtHeight(dungeonLevel.PlayerSpawnPoint().X(), dungeonLevel.PlayerSpawnPoint().Y(), 2, s.player)
|
||||||
|
|
||||||
s.viewport = render.CreateViewport(
|
s.viewport = render.CreateViewport(
|
||||||
util.PositionAt(0, 0),
|
util.PositionAt(0, 0),
|
||||||
util.PositionAt(40, 12),
|
dungeonLevel.PlayerSpawnPoint(),
|
||||||
util.SizeOf(80, 24),
|
util.SizeOf(80, 24),
|
||||||
tcell.StyleDefault,
|
tcell.StyleDefault,
|
||||||
)
|
)
|
||||||
|
@ -44,6 +57,48 @@ func BeginPlayingState() *PlayingState {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
|
||||||
|
rooms := level.Rooms()
|
||||||
|
|
||||||
|
genTable := make(map[float32]*model.ItemType)
|
||||||
|
|
||||||
|
genTable[0.2] = model.ItemTypeFish()
|
||||||
|
genTable[0.05] = model.ItemTypeBow()
|
||||||
|
genTable[0.051] = model.ItemTypeLongsword()
|
||||||
|
genTable[0.052] = model.ItemTypeKey()
|
||||||
|
|
||||||
|
itemTiles := make([]model.Tile, 0, 10)
|
||||||
|
|
||||||
|
for _, r := range rooms {
|
||||||
|
maxItems := int(0.10 * float64(r.Size().Area()))
|
||||||
|
|
||||||
|
if maxItems < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
numItems := rand.Intn(maxItems)
|
||||||
|
|
||||||
|
for range numItems {
|
||||||
|
itemType := model.GenerateItemType(genTable)
|
||||||
|
|
||||||
|
if itemType == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := util.PositionAt(
|
||||||
|
util.RandInt(r.Position().X()+1, r.Position().X()+r.Size().Width()-1),
|
||||||
|
util.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
|
||||||
|
)
|
||||||
|
|
||||||
|
itemTiles = append(itemTiles, model.CreateItemTile(
|
||||||
|
pos, itemType, 1,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemTiles
|
||||||
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) Pause() {
|
func (ps *PlayingState) Pause() {
|
||||||
ps.pauseGame = true
|
ps.pauseGame = true
|
||||||
}
|
}
|
||||||
|
@ -66,15 +121,36 @@ func (ps *PlayingState) MovePlayer() {
|
||||||
tileAtMovePos := ps.level.TileAt(newPlayerPos.XY())
|
tileAtMovePos := ps.level.TileAt(newPlayerPos.XY())
|
||||||
|
|
||||||
if tileAtMovePos.Passable() {
|
if tileAtMovePos.Passable() {
|
||||||
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 1, nil)
|
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, nil)
|
||||||
ps.player.Move(ps.movePlayerDirection)
|
ps.player.Move(ps.movePlayerDirection)
|
||||||
ps.viewport.SetCenter(ps.player.Position())
|
ps.viewport.SetCenter(ps.player.Position())
|
||||||
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 1, ps.player)
|
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, ps.player)
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.movePlayerDirection = model.DirectionNone
|
ps.movePlayerDirection = model.DirectionNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *PlayingState) PickUpItemUnderPlayer() {
|
||||||
|
pos := ps.player.Position()
|
||||||
|
tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1)
|
||||||
|
|
||||||
|
itemTile, ok := tile.(*model.ItemTile)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item := model.CreateItem(itemTile.Type(), itemTile.Quantity())
|
||||||
|
|
||||||
|
success := ps.player.Inventory().Push(item)
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.level.SetTileAtHeight(pos.X(), pos.Y(), 1, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
||||||
ps.player.Input(e)
|
ps.player.Input(e)
|
||||||
|
|
||||||
|
@ -88,6 +164,11 @@ func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Key() == tcell.KeyRune && e.Rune() == 'p' {
|
||||||
|
ps.pickUpUnderPlayer = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch e.Key() {
|
switch e.Key() {
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
ps.movePlayerDirection = model.DirectionUp
|
ps.movePlayerDirection = model.DirectionUp
|
||||||
|
@ -119,6 +200,7 @@ func (ps *PlayingState) OnTick(dt int64) GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.openInventory {
|
if ps.openInventory {
|
||||||
|
ps.openInventory = false
|
||||||
return CreateInventoryScreenState(ps.player, ps)
|
return CreateInventoryScreenState(ps.player, ps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,19 +208,24 @@ func (ps *PlayingState) OnTick(dt int64) GameState {
|
||||||
ps.MovePlayer()
|
ps.MovePlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ps.pickUpUnderPlayer {
|
||||||
|
ps.pickUpUnderPlayer = false
|
||||||
|
ps.PickUpItemUnderPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) CollectDrawables() []render.Drawable {
|
func (ps *PlayingState) CollectDrawables() []render.Drawable {
|
||||||
return render.Multidraw(render.CreateDrawingInstructions(func(v views.View) {
|
return render.Multidraw(render.CreateDrawingInstructions(func(v views.View) {
|
||||||
ps.viewport.DrawFromProvider(v, func(x, y int) rune {
|
ps.viewport.DrawFromProvider(v, func(x, y int) (rune, tcell.Style) {
|
||||||
tile := ps.level.TileAt(x, y)
|
tile := ps.level.TileAt(x, y)
|
||||||
|
|
||||||
if tile != nil {
|
if tile != nil {
|
||||||
return tile.Presentation()
|
return tile.Presentation()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ' '
|
return ' ', tcell.StyleDefault
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ type Grid struct {
|
||||||
numCellsVertical int
|
numCellsVertical int
|
||||||
position util.Position
|
position util.Position
|
||||||
style tcell.Style
|
style tcell.Style
|
||||||
|
highlightStyle tcell.Style
|
||||||
|
|
||||||
northBorder rune
|
northBorder rune
|
||||||
westBorder rune
|
westBorder rune
|
||||||
|
@ -36,6 +37,9 @@ type Grid struct {
|
||||||
horizontalRightTJunction rune
|
horizontalRightTJunction rune
|
||||||
crossJunction rune
|
crossJunction rune
|
||||||
|
|
||||||
|
isHighlighted bool
|
||||||
|
highlightedGrid util.Position
|
||||||
|
|
||||||
fillRune rune
|
fillRune rune
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,15 +48,15 @@ func CreateSimpleGrid(
|
||||||
cellWidth, cellHeight int,
|
cellWidth, cellHeight int,
|
||||||
numCellsHorizontal, numCellsVertical int,
|
numCellsHorizontal, numCellsVertical int,
|
||||||
borderRune, fillRune rune,
|
borderRune, fillRune rune,
|
||||||
style tcell.Style,
|
style tcell.Style, highlightStyle tcell.Style,
|
||||||
) Grid {
|
) *Grid {
|
||||||
return CreateGrid(
|
return CreateGrid(
|
||||||
x, y, cellWidth, cellHeight, numCellsHorizontal, numCellsVertical,
|
x, y, cellWidth, cellHeight, numCellsHorizontal, numCellsVertical,
|
||||||
borderRune, borderRune, borderRune, borderRune,
|
borderRune, borderRune, borderRune, borderRune,
|
||||||
borderRune, fillRune, borderRune, borderRune,
|
borderRune, fillRune, borderRune, borderRune,
|
||||||
borderRune, borderRune, borderRune, borderRune,
|
borderRune, borderRune, borderRune, borderRune,
|
||||||
borderRune, borderRune, borderRune, borderRune,
|
borderRune, borderRune, borderRune, borderRune,
|
||||||
style,
|
style, highlightStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,15 +75,17 @@ func CreateGrid(
|
||||||
westBorder, fillRune, internalVerticalBorder, eastBorder,
|
westBorder, fillRune, internalVerticalBorder, eastBorder,
|
||||||
horizontalRightTJunction, internalHorizontalBorder, crossJunction, horizontalLeftTJunction,
|
horizontalRightTJunction, internalHorizontalBorder, crossJunction, horizontalLeftTJunction,
|
||||||
swCorner, southBorder, verticalUpwardsTJunction, seCorner rune,
|
swCorner, southBorder, verticalUpwardsTJunction, seCorner rune,
|
||||||
style tcell.Style,
|
style tcell.Style, highlightStyle tcell.Style,
|
||||||
) Grid {
|
) *Grid {
|
||||||
return Grid{
|
return &Grid{
|
||||||
id: uuid.New(),
|
id: uuid.New(),
|
||||||
internalCellSize: util.SizeOf(cellWidth, cellHeight),
|
internalCellSize: util.SizeOf(cellWidth, cellHeight),
|
||||||
numCellsHorizontal: numCellsHorizontal,
|
numCellsHorizontal: numCellsHorizontal,
|
||||||
numCellsVertical: numCellsVertical,
|
numCellsVertical: numCellsVertical,
|
||||||
|
isHighlighted: false,
|
||||||
position: util.PositionAt(x, y),
|
position: util.PositionAt(x, y),
|
||||||
style: style,
|
style: style,
|
||||||
|
highlightStyle: highlightStyle,
|
||||||
northBorder: northBorder,
|
northBorder: northBorder,
|
||||||
eastBorder: eastBorder,
|
eastBorder: eastBorder,
|
||||||
southBorder: southBorder,
|
southBorder: southBorder,
|
||||||
|
@ -100,10 +106,19 @@ func CreateGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Grid) UniqueId() uuid.UUID {
|
func (g *Grid) UniqueId() uuid.UUID {
|
||||||
return g.id
|
return g.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Grid) Highlight(highlightedGrid util.Position) {
|
||||||
|
g.isHighlighted = true
|
||||||
|
g.highlightedGrid = highlightedGrid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Grid) Unhighlight() {
|
||||||
|
g.isHighlighted = false
|
||||||
|
}
|
||||||
|
|
||||||
// C###T###T###C
|
// C###T###T###C
|
||||||
// # # # #
|
// # # # #
|
||||||
// # # # #
|
// # # # #
|
||||||
|
@ -117,7 +132,7 @@ func (g Grid) UniqueId() uuid.UUID {
|
||||||
// # # # #
|
// # # # #
|
||||||
// # # # #
|
// # # # #
|
||||||
// C###T###T###C
|
// C###T###T###C
|
||||||
func (g Grid) drawBorders(v views.View) {
|
func (g *Grid) drawBorders(v views.View) {
|
||||||
iCellSizeWidth := g.internalCellSize.Width()
|
iCellSizeWidth := g.internalCellSize.Width()
|
||||||
iCellSizeHeight := g.internalCellSize.Height()
|
iCellSizeHeight := g.internalCellSize.Height()
|
||||||
width := 1 + (iCellSizeWidth * int(g.numCellsHorizontal)) + (int(g.numCellsHorizontal))
|
width := 1 + (iCellSizeWidth * int(g.numCellsHorizontal)) + (int(g.numCellsHorizontal))
|
||||||
|
@ -125,59 +140,61 @@ func (g Grid) drawBorders(v views.View) {
|
||||||
x := g.position.X()
|
x := g.position.X()
|
||||||
y := g.position.Y()
|
y := g.position.Y()
|
||||||
|
|
||||||
v.SetContent(x, y, g.nwCorner, nil, g.style)
|
style := g.style
|
||||||
v.SetContent(x+width-1, y, g.neCorner, nil, g.style)
|
|
||||||
v.SetContent(x, y+height-1, g.swCorner, nil, g.style)
|
|
||||||
v.SetContent(x+width-1, y+height-1, g.seCorner, nil, g.style)
|
|
||||||
|
|
||||||
for w := 1; w < width-1; w++ {
|
for w := 0; w < width; w++ {
|
||||||
|
for iw := 1; iw < g.numCellsVertical; iw++ {
|
||||||
for iw := 1; iw < int(g.numCellsVertical); iw++ {
|
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.internalHorizontalBorder, nil, style)
|
||||||
if w%(iCellSizeWidth+1) == 0 {
|
|
||||||
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.crossJunction, nil, g.style)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.internalHorizontalBorder, nil, g.style)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if w%(iCellSizeWidth+1) == 0 {
|
if w%(iCellSizeWidth+1) == 0 {
|
||||||
v.SetContent(x+w, y, g.verticalDownwardsTJunction, nil, g.style)
|
v.SetContent(x+w, y, g.verticalDownwardsTJunction, nil, style)
|
||||||
v.SetContent(x+w, y+height-1, g.verticalUpwardsTJunction, nil, g.style)
|
v.SetContent(x+w, y+height-1, g.verticalUpwardsTJunction, nil, style)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v.SetContent(x+w, y, g.northBorder, nil, g.style)
|
v.SetContent(x+w, y, g.northBorder, nil, style)
|
||||||
v.SetContent(x+w, y+height-1, g.southBorder, nil, g.style)
|
v.SetContent(x+w, y+height-1, g.southBorder, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
for h := 1; h < height-1; h++ {
|
for h := 0; h < height; h++ {
|
||||||
|
if h == 0 {
|
||||||
|
v.SetContent(x, y, g.nwCorner, nil, style)
|
||||||
|
v.SetContent(x, y+height-1, g.swCorner, nil, style)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for ih := 1; ih < int(g.numCellsHorizontal); ih++ {
|
if h == height-1 {
|
||||||
|
v.SetContent(x+width-1, y, g.neCorner, nil, style)
|
||||||
|
v.SetContent(x+width-1, y+height-1, g.seCorner, nil, style)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for ih := 1; ih < g.numCellsHorizontal; ih++ {
|
||||||
if h%(iCellSizeHeight+1) == 0 {
|
if h%(iCellSizeHeight+1) == 0 {
|
||||||
v.SetContent(x+(ih*iCellSizeHeight+ih), y+h, g.crossJunction, nil, g.style)
|
v.SetContent(x+(ih*iCellSizeWidth+ih), y+h, g.crossJunction, nil, style)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v.SetContent(x+(ih*iCellSizeHeight+ih), y+h, g.internalVerticalBorder, nil, g.style)
|
v.SetContent(x+(ih*iCellSizeWidth+ih), y+h, g.internalVerticalBorder, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
if h%(iCellSizeHeight+1) == 0 {
|
if h%(iCellSizeHeight+1) == 0 {
|
||||||
v.SetContent(x, y+h, g.horizontalRightTJunction, nil, g.style)
|
v.SetContent(x, y+h, g.horizontalRightTJunction, nil, style)
|
||||||
v.SetContent(x+width-1, y+h, g.horizontalLeftTJunction, nil, g.style)
|
v.SetContent(x+width-1, y+h, g.horizontalLeftTJunction, nil, style)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v.SetContent(x, y+h, g.westBorder, nil, g.style)
|
v.SetContent(x, y+h, g.westBorder, nil, style)
|
||||||
v.SetContent(x+width-1, y+h, g.eastBorder, nil, g.style)
|
v.SetContent(x+width-1, y+h, g.eastBorder, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Grid) drawFill(v views.View) {
|
func (g *Grid) drawFill(v views.View) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Grid) Draw(v views.View) {
|
func (g *Grid) Draw(v views.View) {
|
||||||
g.drawBorders(v)
|
g.drawBorders(v)
|
||||||
g.drawFill(v)
|
g.drawFill(v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,9 @@ func (c *RenderContext) Draw(deltaTime int64, drawables []Drawable) {
|
||||||
|
|
||||||
c.view.Clear()
|
c.view.Clear()
|
||||||
|
|
||||||
fpsText := CreateText(0, 0, 16, 1, fmt.Sprintf("%v FPS", fps), tcell.StyleDefault)
|
msPerFrame := float32(fps) / 1000.0
|
||||||
|
|
||||||
|
fpsText := CreateText(0, 0, 16, 1, fmt.Sprintf("%vms", msPerFrame), tcell.StyleDefault)
|
||||||
|
|
||||||
for _, d := range drawables {
|
for _, d := range drawables {
|
||||||
d.Draw(c.view)
|
d.Draw(c.view)
|
||||||
|
|
|
@ -69,8 +69,11 @@ func (t *Text) Draw(s views.View) {
|
||||||
currentVPos := 0
|
currentVPos := 0
|
||||||
|
|
||||||
drawText := func(text string) {
|
drawText := func(text string) {
|
||||||
for i, r := range text {
|
lastPos := 0
|
||||||
s.SetContent(x+currentHPos+i, y+currentVPos, r, nil, t.style)
|
|
||||||
|
for _, r := range text {
|
||||||
|
s.SetContent(x+currentHPos+lastPos, y+currentVPos, r, nil, t.style)
|
||||||
|
lastPos++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,14 +50,15 @@ func (vp *Viewport) ScreenLocation() util.Position {
|
||||||
return vp.screenLocation
|
return vp.screenLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vp *Viewport) DrawFromProvider(v views.View, provider func(x, y int) rune) {
|
func (vp *Viewport) DrawFromProvider(v views.View, provider func(x, y int) (rune, tcell.Style)) {
|
||||||
width, height := vp.viewportSize.WH()
|
width, height := vp.viewportSize.WH()
|
||||||
originX, originY := vp.viewportCenter.WithOffset(-width/2, -height/2).XY()
|
originX, originY := vp.viewportCenter.WithOffset(-width/2, -height/2).XY()
|
||||||
screenX, screenY := vp.screenLocation.XY()
|
screenX, screenY := vp.screenLocation.XY()
|
||||||
|
|
||||||
for h := originY; h < originY+height; h++ {
|
for h := originY; h < originY+height; h++ {
|
||||||
for w := originX; w < originX+width; w++ {
|
for w := originX; w < originX+width; w++ {
|
||||||
v.SetContent(screenX, screenY, provider(w, h), nil, vp.style)
|
r, style := provider(w, h)
|
||||||
|
v.SetContent(screenX, screenY, r, nil, style)
|
||||||
|
|
||||||
screenX += 1
|
screenX += 1
|
||||||
}
|
}
|
||||||
|
|
14
util/util.go
14
util/util.go
|
@ -1,5 +1,7 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
import "math/rand"
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
x int
|
x int
|
||||||
y int
|
y int
|
||||||
|
@ -36,10 +38,6 @@ func SizeOf(width int, height int) Size {
|
||||||
return Size{int(width), int(height)}
|
return Size{int(width), int(height)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SizeOfInt(width int, height int) Size {
|
|
||||||
return Size{width, height}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Size) Width() int {
|
func (s Size) Width() int {
|
||||||
return s.width
|
return s.width
|
||||||
}
|
}
|
||||||
|
@ -52,6 +50,10 @@ func (s Size) WH() (int, int) {
|
||||||
return s.width, s.height
|
return s.width, s.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Size) Area() int {
|
||||||
|
return s.width * s.height
|
||||||
|
}
|
||||||
|
|
||||||
func LimitIncrement(i int, limit int) int {
|
func LimitIncrement(i int, limit int) int {
|
||||||
if (i + 1) > limit {
|
if (i + 1) > limit {
|
||||||
return i
|
return i
|
||||||
|
@ -67,3 +69,7 @@ func LimitDecrement(i int, limit int) int {
|
||||||
|
|
||||||
return i - 1
|
return i - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RandInt(min, max int) int {
|
||||||
|
return min + rand.Intn(max-min)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue