fixes to viewport, config tuning

This commit is contained in:
thefish 2019-11-01 15:03:52 +03:00
parent a91351d3dc
commit c6c6b6254d
11 changed files with 228 additions and 173 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap" "lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap/mapgens" "lab.zaar.be/thefish/alchemyst-go/engine/gamemap/mapgens"
"lab.zaar.be/thefish/alchemyst-go/engine/types"
"lab.zaar.be/thefish/alchemyst-go/ui" "lab.zaar.be/thefish/alchemyst-go/ui"
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow" "lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
"lab.zaar.be/thefish/alchemyst-go/util" "lab.zaar.be/thefish/alchemyst-go/util"
@ -22,23 +23,6 @@ func init() {
runtime.LockOSThread() runtime.LockOSThread()
} }
type GameState struct {
mainfunc chan func()
exit chan struct{}
input chan string
rawInput chan int
}
// do runs f on the main thread.
func (*GameState) Do(f func()) {
done := make(chan struct{}, 1)
State.mainfunc <- func() {
f()
done <- struct{}{}
}
<-done
}
//we can run logic in separate goroutines //we can run logic in separate goroutines
// //
// go doSometing(State,...) // go doSometing(State,...)
@ -52,11 +36,13 @@ func (*GameState) Do(f func()) {
// ... // ...
// } // }
var State = GameState{ var State = types.GameState{
mainfunc: make(chan func()), Mainfunc: make(chan func()),
exit: make(chan struct{}, 1), Exit: make(chan struct{}, 1),
input: make(chan string, 1), Input: make(chan string, 1),
rawInput: make(chan int, 1), RawInput: make(chan int, 1),
FovRecompute: make(chan struct{},1),
Redraw: make(chan struct{},1),
} }
func main() { func main() {
@ -72,35 +58,37 @@ func main() {
setupLayers(mw) setupLayers(mw)
level := gamemap.NewLevel(mainCtx, "test", 1) level, rooms := mapgens.DefaultGen(gamemap.NewLevel(mainCtx, "test", 1))
level = mapgens.DefaultGen(level)
vp := mainwindow.NewViewPort(40, 0, 60, 47, level, mw.GetLayer("base")) vp := mainwindow.NewViewPort(40, 0, 60, 47, level, mw.GetLayer("base"))
vp.Render() vp.PlayerCoords = rooms[0].Center
vp.Render(State)
go decodeInput(mainCtx, mw.GetLayer("base")) go decodeInput(mainCtx, mw.GetLayer("base"))
go vp.Listen(State)
//but every call to bearlibterminal must be wrapped to closure and passed to mainfunc //but every call to bearlibterminal must be wrapped to closure and passed to mainfunc
var exit = false var exit = false
for !exit { for !exit {
select { select {
case State.rawInput <- ui.ReadKeyCode(): case State.RawInput <- ui.ReadKeyCode():
break break
case pressed := <-State.input: case pressed := <-State.Input:
mw.GetLayer("base").ClearArea(0, 3, 40, 1) mw.GetLayer("base").ClearArea(0, 3, 40, 1)
mw.GetLayer("base").Print(1, 3, "Key: "+pressed) mw.GetLayer("base").Print(1, 3, "Key: "+pressed)
mw.GetLayer("base").Print(1, 6, "█") mw.GetLayer("base").Print(1, 6, "█")
break break
//case f := <-State.mainfunc: //case f := <-State.mainfunc:
// f() // f()
// break // break
case <-State.exit: case <-State.Exit:
mainCtx.Logger().Warn().Msg("quitting NOW") mainCtx.Logger().Warn().Msg("quitting NOW")
exit = true exit = true
break break
// не оставляйте default в бесконесчном select {} - сожрет всё CPU // не оставляйте default в бесконесчном select {} - сожрет всё CPU
default: default:
vp.Render() vp.Render(State)
blt.Refresh() blt.Refresh()
} }
@ -117,20 +105,20 @@ func setupLayers(mainwindow *mainwindow.MainWindow) {
func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) { func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) {
var exit = false var exit = false
var waitForWCspam = true var waitForWCspam = true
for !exit{ for !exit {
select { select {
case keycode := <-State.rawInput: case keycode := <-State.RawInput:
if keycode == blt.TK_NONE { if keycode == blt.TK_NONE {
continue continue
} }
if keycode == blt.TK_CLOSE && !waitForWCspam { if keycode == blt.TK_CLOSE && !waitForWCspam {
ctx.Logger().Warn().Msg("exiting on window close...") ctx.Logger().Warn().Msg("exiting on window close...")
State.exit <- struct{}{} State.Exit <- struct{}{}
ctx.Logger().Warn().Msg("...done") ctx.Logger().Warn().Msg("...done")
return return
} }
var pressed= "" var pressed = ""
var isModifier, _= util.InArray(keycode, modifiers) var isModifier, _ = util.InArray(keycode, modifiers)
if !isModifier { if !isModifier {
pressed = ui.Scancodemap[keycode] pressed = ui.Scancodemap[keycode]
@ -147,6 +135,7 @@ func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) {
//global hotkeys //global hotkeys
switch pressed { switch pressed {
//fixme testing only
case "F10": case "F10":
State.Do(func() { State.Do(func() {
blt.Set("window: size=100x47; font: ./resources/fonts-ttf/UbuntuMono-R.ttf, size=11;") blt.Set("window: size=100x47; font: ./resources/fonts-ttf/UbuntuMono-R.ttf, size=11;")
@ -155,13 +144,15 @@ func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) {
fallthrough fallthrough
case "Escape": case "Escape":
ctx.Logger().Info().Msg("exiting on quit command...") ctx.Logger().Info().Msg("exiting on quit command...")
State.exit <- struct{}{} State.Exit <- struct{}{}
ctx.Logger().Info().Msg("...done") ctx.Logger().Info().Msg("...done")
exit = true exit = true
return return
default: default:
waitForWCspam = false; if pressed != "" {
State.input <- pressed waitForWCspam = false;
State.Input <- pressed
}
} }
} }
} }

View File

@ -4,6 +4,7 @@
"sizeX": 100, "sizeX": 100,
"sizeY": 47, "sizeY": 47,
"fpsLimit" : 60, "fpsLimit" : 60,
"font": "./resources/fonts-ttf/LiberationMono-Bold.ttf", "font": "./resources/fonts-ttf/UbuntuMono-R.ttf",
"fontSize": "12x16",
"verbosity": "debug" "verbosity": "debug"
} }

View File

@ -7,8 +7,8 @@ import (
) )
//fixme move to config //fixme move to config
var mapWidth = 150 var mapWidth = 70
var mapHeight = 100 var mapHeight = 50
type Level struct { type Level struct {

View File

@ -11,7 +11,7 @@ var maxRoomSize = 22
var maxrooms = 30 var maxrooms = 30
//fixme make closure to stack them //fixme make closure to stack them
func DefaultGen(l *gamemap.Level) *gamemap.Level { func DefaultGen(l *gamemap.Level) (*gamemap.Level, []*gamemap.Room) {
rng := util.NewRNG() rng := util.NewRNG()
@ -56,6 +56,9 @@ func DefaultGen(l *gamemap.Level) *gamemap.Level {
if !failed { if !failed {
rooms = append(rooms, newRoom) rooms = append(rooms, newRoom)
} }
//addStairs(rooms)
//itemize(rooms)
} }
//fillage := types.RectFill{ //fillage := types.RectFill{
@ -89,7 +92,7 @@ func DefaultGen(l *gamemap.Level) *gamemap.Level {
} }
} }
return l return l, rooms
} }
func connectRooms (l *gamemap.Level, room, otherRoom *gamemap.Room, fillage types.RectFill, toss int) { func connectRooms (l *gamemap.Level, room, otherRoom *gamemap.Room, fillage types.RectFill, toss int) {

View File

@ -1,69 +1,8 @@
package gamemap package gamemap
import ( import (
"github.com/gammazero/deque" . "lab.zaar.be/thefish/alchemyst-go/engine/types"
"lab.zaar.be/thefish/alchemyst-go/util"
) )
import blt "lab.zaar.be/thefish/bearlibterminal"
type ColorHolder interface {
GetColor() uint32
}
type cdeque struct {
deque.Deque
}
func (c *cdeque) Next() uint8 {
c.Rotate(1)
return c.Front().(uint8)
}
type DanceColorHolder struct {
A uint8
R *cdeque
G *cdeque
B *cdeque
}
func (chd *DanceColorHolder) GetColor() uint32 {
return blt.ColorFromARGB(
chd.A,
chd.R.Next(),
chd.G.Next(),
chd.B.Next(),
)
}
type PlainColorHolder struct {
A uint8
R uint8
G uint8
B uint8
}
func (chb *PlainColorHolder) GetColor() uint32 {
return blt.ColorFromARGB(
chb.A,
chb.R,
chb.G,
chb.B,
)
}
type TileColorSet struct {
Fg ColorHolder
Bg ColorHolder
DarkFg ColorHolder
DarkBg ColorHolder
}
type Appearance struct {
Char string `json:"char"`
ColorSet *TileColorSet `json:"colorSet"`
}
var crng = util.NewRNG()
type Tile struct { type Tile struct {
*Appearance `json:"app"` *Appearance `json:"app"`
@ -97,33 +36,6 @@ func (t *Tile) GetRawBgColor() uint32 {
} }
} }
func singleColorRing(colorValue uint8) *cdeque {
c := &cdeque{}
c.PushBack(colorValue)
return c
}
func fillColorRing(colorValue uint8, minGlow, maxGlow, step int) *cdeque {
q := make([]uint8, 0)
color := int(colorValue)
for color < maxGlow {
q = append(q, uint8(color))
color = crng.Range(1, step) + color
}
color = crng.Range(0, step+minGlow)
q = append(q, uint8(color))
//for uint8(color) < uint8(colorValue) {
// q = append(q, uint8(color))
// color = crng.Range(1, step+minGlow)
//}
c := &cdeque{}
for _, v := range q {
c.PushBack(uint8(v))
}
return c
}
func NewWall() *Tile { func NewWall() *Tile {
return &Tile{ return &Tile{
Name: "Wall", Name: "Wall",
@ -178,14 +90,14 @@ func NewWaterTile() *Tile {
Appearance: &Appearance{ Appearance: &Appearance{
Char: " ", Char: " ",
ColorSet: &TileColorSet{ ColorSet: &TileColorSet{
Fg: &PlainColorHolder{255, 220, 220, 250}, Fg: &PlainColorHolder{255, 220, 220, 250},
Bg: &DanceColorHolder{ Bg: &DanceColorHolder{
255, 255,
singleColorRing(19), SingleColorRing(19),
fillColorRing(19, 0, 15, 2), FillColorRing(19, 0, 15, 2),
fillColorRing(70, 120, 220, 12), FillColorRing(70, 120, 220, 12),
}, },
DarkFg: &PlainColorHolder{255, 30, 20, 50 }, DarkFg: &PlainColorHolder{255, 30, 20, 50},
DarkBg: &PlainColorHolder{255, 7, 7, 30}, DarkBg: &PlainColorHolder{255, 7, 7, 30},
}, },
}, },
@ -205,16 +117,16 @@ func NewDeepWaterTile() *Tile {
Appearance: &Appearance{ Appearance: &Appearance{
Char: " ", Char: " ",
ColorSet: &TileColorSet{ ColorSet: &TileColorSet{
Fg: &PlainColorHolder{255, 220, 220, 250}, Fg: &PlainColorHolder{255, 220, 220, 250},
Bg: &DanceColorHolder{ Bg: &DanceColorHolder{
255, 255,
singleColorRing(5), SingleColorRing(5),
fillColorRing(2,2,42,4), FillColorRing(2, 2, 42, 4),
fillColorRing(154, 150, 229, 12), FillColorRing(154, 150, 229, 12),
}, },
DarkFg: &PlainColorHolder{255, 30, 20, 50}, DarkFg: &PlainColorHolder{255, 30, 20, 50},
DarkBg: &PlainColorHolder{255, 7, 7, 30}, DarkBg: &PlainColorHolder{255, 7, 7, 30},
}, },
}, },
} }
} }

View File

@ -0,0 +1,94 @@
package types
import (
"github.com/gammazero/deque"
"lab.zaar.be/thefish/alchemyst-go/util"
)
import blt "lab.zaar.be/thefish/bearlibterminal"
var crng = util.NewRNG()
type ColorHolder interface {
GetColor() uint32
}
type cdeque struct {
deque.Deque
}
func (c *cdeque) Next() uint8 {
c.Rotate(1)
return c.Front().(uint8)
}
type DanceColorHolder struct {
A uint8
R *cdeque
G *cdeque
B *cdeque
}
func (chd *DanceColorHolder) GetColor() uint32 {
return blt.ColorFromARGB(
chd.A,
chd.R.Next(),
chd.G.Next(),
chd.B.Next(),
)
}
type PlainColorHolder struct {
A uint8
R uint8
G uint8
B uint8
}
func (chb *PlainColorHolder) GetColor() uint32 {
return blt.ColorFromARGB(
chb.A,
chb.R,
chb.G,
chb.B,
)
}
type TileColorSet struct {
Fg ColorHolder
Bg ColorHolder
DarkFg ColorHolder
DarkBg ColorHolder
}
type Appearance struct {
Char string `json:"char"`
ColorSet *TileColorSet `json:"colorSet"`
}
func SingleColorRing(colorValue uint8) *cdeque {
c := &cdeque{}
c.PushBack(colorValue)
return c
}
func FillColorRing(colorValue uint8, minGlow, maxGlow, step int) *cdeque {
q := make([]uint8, 0)
color := int(colorValue)
for color < maxGlow {
q = append(q, uint8(color))
color = crng.Range(1, step) + color
}
color = crng.Range(0, step+minGlow)
q = append(q, uint8(color))
//for uint8(color) < uint8(colorValue) {
// q = append(q, uint8(color))
// color = crng.Range(1, step+minGlow)
//}
c := &cdeque{}
for _, v := range q {
c.PushBack(uint8(v))
}
return c
}

20
engine/types/gamestate.go Normal file
View File

@ -0,0 +1,20 @@
package types
type GameState struct {
Mainfunc chan func()
Exit chan struct{}
Input chan string
RawInput chan int
FovRecompute chan struct{}
Redraw chan struct{}
}
// do runs f on the main thread.
func (g *GameState) Do(f func()) {
done := make(chan struct{}, 1)
g.Mainfunc <- func() {
f()
done <- struct{}{}
}
<-done
}

View File

@ -98,7 +98,7 @@ func (layer *Layer) Decorate(f func(args ...interface{})) func(args ...interface
} }
} }
func (layer *Layer) Clear(r *types.Rect) { func (layer *Layer) ClearRect(r *types.Rect) {
blt.ClearArea(r.X, r.Y, r.W, r.H) blt.ClearArea(r.X, r.Y, r.W, r.H)
} }

View File

@ -42,13 +42,14 @@ func (mw *MainWindow) Open() {
blt.Set( blt.Set(
fmt.Sprintf( fmt.Sprintf(
//"window: size=%dx%d, title='%s v%s'; font: ./resources/fonts-bitmap/ibmnew8x12.png, size=8x12;", //"window: size=%dx%d, title='%s v%s'; font: ./resources/fonts-bitmap/ibmnew8x12.png, size=8x12;",
"window: size=%dx%d, title='%s v%s'; font: %s, size=8x16;", "window: size=%dx%d, title='%s v%s'; font: %s, size=%s;",
//"window: size=%dx%d, title='%s v%s'", //"window: size=%dx%d, title='%s v%s'",
config.MainWindowSizeX, config.MainWindowSizeX,
config.MainWindowSizeY, config.MainWindowSizeY,
config.Title, config.Title,
config.Version, config.Version,
config.Font, config.Font,
config.FontSize,
), ),
) )
} }

View File

@ -2,6 +2,7 @@ package mainwindow
import ( import (
"errors" "errors"
"fmt"
"lab.zaar.be/thefish/alchemyst-go/engine/fov" "lab.zaar.be/thefish/alchemyst-go/engine/fov"
"lab.zaar.be/thefish/alchemyst-go/engine/fov/precomputed_shade" "lab.zaar.be/thefish/alchemyst-go/engine/fov/precomputed_shade"
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap" "lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
@ -10,15 +11,14 @@ import (
var NotInViewError = errors.New("not in ViewPort") var NotInViewError = errors.New("not in ViewPort")
const FPS_LIMIT = 60
type ViewPort struct { type ViewPort struct {
*types.Rect *types.Rect
cameraCoords types.Coords
level *gamemap.Level level *gamemap.Level
layer *Layer layer *Layer
Fov fov.Fov Fov fov.Fov
playerCoords types.Coords PlayerCoords types.Coords
playerTorchRadius int PlayerTorchRadius int
} }
func NewViewPort(x, y, w, h int, level *gamemap.Level, layer *Layer) *ViewPort { func NewViewPort(x, y, w, h int, level *gamemap.Level, layer *Layer) *ViewPort {
@ -32,13 +32,13 @@ func NewViewPort(x, y, w, h int, level *gamemap.Level, layer *Layer) *ViewPort {
Fov: fov, Fov: fov,
} }
vp.playerCoords = types.Coords{10, 10} vp.PlayerCoords = types.Coords{10, 10}
vp.playerTorchRadius = 10 vp.PlayerTorchRadius = 10
return &vp return &vp
} }
func (vp *ViewPort) Move(c *types.Coords) { func (vp *ViewPort) Move(c *types.Coords, state types.GameState) {
x := c.X - vp.Rect.W/2 x := c.X - vp.Rect.W/2
y := c.Y - vp.Rect.H/2 y := c.Y - vp.Rect.H/2
@ -49,22 +49,26 @@ func (vp *ViewPort) Move(c *types.Coords) {
y = 0 y = 0
} }
if x > vp.level.W-vp.W { if x > vp.level.W-vp.W {
x = vp.level.W - vp.W x = vp.level.W - vp.W - 1
} }
if y > vp.level.H-vp.H { if y > vp.level.H-vp.H {
x = vp.level.H - vp.H x = vp.level.H - vp.H - 1
} }
//if x != vp.X || y != vp.Y { State.FovRecompute <- struct{}{}} if x != vp.cameraCoords.X || y != vp.cameraCoords.Y {
vp.X, vp.Y = x, y state.FovRecompute <- struct{}{}
}
vp.cameraCoords.X = x
vp.cameraCoords.Y = y
} }
func (vp *ViewPort) ToVPCoords(c *types.Coords) (newCoords *types.Coords, err error) { func (vp *ViewPort) ToVPCoords(c types.Coords) (newCoords types.Coords, err error) {
//coords on map to coords on vp //coords on map to coords on vp
x, y := c.X-vp.X, c.Y-vp.Y x, y := c.X-vp.cameraCoords.X, c.Y-vp.cameraCoords.Y
if x < 0 || y < 0 || x >= vp.W || y >= vp.H { if x < 0 || y < 0 || x > vp.W || y > vp.H {
return &types.Coords{-1, -1}, NotInViewError return types.Coords{-1, -1}, NotInViewError
} }
return &types.Coords{x, y}, nil return types.Coords{x, y}, nil
} }
////call only from main thread ////call only from main thread
@ -75,15 +79,15 @@ func (vp *ViewPort) ToVPCoords(c *types.Coords) (newCoords *types.Coords, err er
// redraw := false // redraw := false
// //fixme get player instance // //fixme get player instance
// //
// vp.Move(&vp.playerCoords) // vp.Move(&vp.PlayerCoords)
// //fixme detect fovRecompute // //fixme detect fovRecompute
// if fovRecompute { // if fovRecompute {
// vp.layer.Clear(vp.Rect) // vp.layer.ClearRect(vp.Rect)
// fovRecompute = false // fovRecompute = false
// redraw = true // redraw = true
// //fixme // //fixme
// //
// vp.Fov.ComputeFov(vp.level, vp.playerCoords, vp.playerTorchRadius) // vp.Fov.ComputeFov(vp.level, vp.PlayerCoords, vp.PlayerTorchRadius)
// } // }
// //increase ticker // //increase ticker
// fpsTicker++ // fpsTicker++
@ -119,26 +123,54 @@ func (vp *ViewPort) ToVPCoords(c *types.Coords) (newCoords *types.Coords, err er
var redraw = true var redraw = true
var fovRecompute = true var fovRecompute = true
func (vp *ViewPort) Render() { func (vp *ViewPort) Listen(state types.GameState) {
for {
select {
case <-state.FovRecompute:
fovRecompute = true
case <-state.Redraw:
redraw = true
}
}
}
func (vp *ViewPort) Render(state types.GameState) {
vp.Move(&vp.PlayerCoords, state)
if fovRecompute { if fovRecompute {
vp.layer.Clear(vp.Rect) vp.layer.ClearRect(vp.Rect)
fovRecompute = false fovRecompute = false
redraw = true redraw = true
vp.Fov.ComputeFov(vp.level, vp.playerCoords, vp.playerTorchRadius) vp.Fov.ComputeFov(vp.level, vp.PlayerCoords, vp.PlayerTorchRadius)
} }
if redraw { if redraw {
//terrain
for y := 0; y < vp.H; y++ { for y := 0; y < vp.H; y++ {
for x := 0; x < vp.W; x++ { for x := 0; x < vp.W; x++ {
mapCoords := types.Coords{vp.X + x, vp.Y + y} mapCoords := types.Coords{vp.cameraCoords.X + x, vp.cameraCoords.Y + y}
tile := vp.level.GetTile(mapCoords) if vp.level.InBounds(mapCoords) {
if tile.Explored || tile.MustDraw || tile.Visible { tile := vp.level.GetTile(mapCoords)
vp.layer.PutToBase(mapCoords.X, mapCoords.Y, tile.GetChar(), tile.GetRawColor(), tile.GetRawBgColor()) if tile.Explored || tile.MustDraw || tile.Visible {
vp.layer.PutToBase(x + vp.X, y + vp.Y, tile.GetChar(), tile.GetRawColor(), tile.GetRawBgColor())
}
} }
} }
} }
//mobs
pc,err := vp.ToVPCoords(vp.PlayerCoords)
_ = pc
if err != nil {
fmt.Println("error on getting player position")
} else {
vp.layer.WithColor("white").Put(pc.X + vp.X, pc.Y + vp.Y, "@")
//mw.GetLayer("base").WithColor("white").Put(42, 10, "B")
//mw.GetLayer("overlay").WithColor("white").Put(59, 10, "O")
}
redraw = true
//redraw = false //redraw = false
} }

View File

@ -14,6 +14,7 @@ type Config struct {
MainWindowSizeX int `json:"sizeX"` MainWindowSizeX int `json:"sizeX"`
MainWindowSizeY int `json:"sizeY"` MainWindowSizeY int `json:"sizeY"`
Font string `json:"font"` Font string `json:"font"`
FontSize string `json:"fontSize"` //format is "8x12"
Verbosity string `json:"verbosity"` Verbosity string `json:"verbosity"`
} }