Generate dungeon layout
This commit is contained in:
parent
05cda8379b
commit
e4cab469d8
5
creep.go
5
creep.go
|
@ -151,12 +151,11 @@ func (c *gameCreep) Update() {
|
|||
}
|
||||
|
||||
x, y := c.x+c.moveX, c.y+c.moveY
|
||||
clampX, clampY := c.level.Clamp(x, y)
|
||||
c.x, c.y = clampX, clampY
|
||||
if clampX != x || clampY != y {
|
||||
if !c.level.isFloor(x, y, false) {
|
||||
c.nextAction = 0
|
||||
return
|
||||
}
|
||||
c.x, c.y = x, y
|
||||
|
||||
if repelled {
|
||||
dx, dy := deltaXY(c.x, c.y, c.player.x, c.player.y)
|
||||
|
|
129
game.go
129
game.go
|
@ -41,6 +41,8 @@ const (
|
|||
|
||||
garlicActiveTime = 7 * time.Second
|
||||
holyWaterActiveTime = time.Second
|
||||
|
||||
maxCreeps = 3000 // TODO optimize and raise
|
||||
)
|
||||
|
||||
var startButtons = []ebiten.StandardGamepadButton{
|
||||
|
@ -390,8 +392,14 @@ func (g *game) reset() error {
|
|||
g.player.health = 3
|
||||
|
||||
// Position player.
|
||||
g.player.x = float64(rand.Intn(108))
|
||||
g.player.y = float64(rand.Intn(108))
|
||||
for {
|
||||
g.player.x = float64(rand.Intn(108))
|
||||
g.player.y = float64(rand.Intn(108))
|
||||
|
||||
if g.level.isFloor(g.player.x, g.player.y, false) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Remove projectiles.
|
||||
g.projectiles = nil
|
||||
|
@ -414,20 +422,19 @@ func (g *game) reset() error {
|
|||
added[addedItem] = true
|
||||
}
|
||||
// Spawn starting garlic.
|
||||
garlicOffsetA := 8 - float64(rand.Intn(16))
|
||||
garlicOffsetB := 8 - float64(rand.Intn(16))
|
||||
startingGarlicX := g.player.x + 2 + garlicOffsetA
|
||||
startingGarlicY := g.player.y + 2 + garlicOffsetB
|
||||
clampX, clampY := g.level.Clamp(startingGarlicX, startingGarlicY)
|
||||
if clampX != startingGarlicX {
|
||||
startingGarlicX = g.player.x - 2 - garlicOffsetA
|
||||
}
|
||||
if clampY != startingGarlicY {
|
||||
startingGarlicY = g.player.y - 2 - garlicOffsetB
|
||||
}
|
||||
item := g.newItem(itemTypeGarlic)
|
||||
item.x = startingGarlicX
|
||||
item.y = startingGarlicY
|
||||
for {
|
||||
garlicOffsetA := 8 - float64(rand.Intn(16))
|
||||
garlicOffsetB := 8 - float64(rand.Intn(16))
|
||||
startingGarlicX := g.player.x + 2 + garlicOffsetA
|
||||
startingGarlicY := g.player.y + 2 + garlicOffsetB
|
||||
|
||||
if g.level.isFloor(startingGarlicX, startingGarlicY, false) {
|
||||
item.x = startingGarlicX
|
||||
item.y = startingGarlicY
|
||||
break
|
||||
}
|
||||
}
|
||||
g.level.items = append(g.level.items, item)
|
||||
|
||||
// Spawn creeps.
|
||||
|
@ -587,11 +594,11 @@ func (g *game) Update() error {
|
|||
g.level.liveCreeps = liveCreeps
|
||||
|
||||
// Clamp target zoom level.
|
||||
if g.camScaleTo < 2 {
|
||||
/*if g.camScaleTo < 2 {
|
||||
g.camScaleTo = 2
|
||||
} else if g.camScaleTo > 4 {
|
||||
g.camScaleTo = 4
|
||||
}
|
||||
} TODO */
|
||||
|
||||
// Smooth zoom transition.
|
||||
div := 10.0
|
||||
|
@ -604,12 +611,13 @@ func (g *game) Update() error {
|
|||
pan := 0.05
|
||||
|
||||
// Pan camera.
|
||||
px, py := g.player.x, g.player.y
|
||||
if g.activeGamepad != -1 {
|
||||
h := ebiten.StandardGamepadAxisValue(g.activeGamepad, ebiten.StandardGamepadAxisLeftStickHorizontal)
|
||||
v := ebiten.StandardGamepadAxisValue(g.activeGamepad, ebiten.StandardGamepadAxisLeftStickVertical)
|
||||
if v < -gamepadDeadZone || v > gamepadDeadZone || h < -gamepadDeadZone || h > gamepadDeadZone {
|
||||
g.player.x += h * pan
|
||||
g.player.y += v * pan
|
||||
px += h * pan
|
||||
py += v * pan
|
||||
}
|
||||
} else {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyShift) {
|
||||
|
@ -617,22 +625,25 @@ func (g *game) Update() error {
|
|||
}
|
||||
|
||||
if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
|
||||
g.player.x -= pan
|
||||
px -= pan
|
||||
}
|
||||
if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
|
||||
g.player.x += pan
|
||||
px += pan
|
||||
}
|
||||
if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
|
||||
g.player.y += pan
|
||||
py += pan
|
||||
}
|
||||
if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
|
||||
g.player.y -= pan
|
||||
py -= pan
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp camera position.
|
||||
if !g.noclipMode {
|
||||
g.player.x, g.player.y = g.level.Clamp(g.player.x, g.player.y)
|
||||
if g.noclipMode || g.level.isFloor(px, py, false) {
|
||||
g.player.x, g.player.y = px, py
|
||||
} else if g.level.isFloor(px, g.player.y, false) {
|
||||
g.player.x = px
|
||||
} else if g.level.isFloor(g.player.x, py, false) {
|
||||
g.player.y = py
|
||||
}
|
||||
|
||||
for _, item := range g.level.items {
|
||||
|
@ -709,11 +720,11 @@ UPDATEPROJECTILES:
|
|||
continue UPDATEPROJECTILES
|
||||
}
|
||||
|
||||
clampX, clampY := g.level.Clamp(p.x, p.y)
|
||||
if clampX != p.x || clampY != p.y {
|
||||
if !g.level.isFloor(p.x, p.y, true) {
|
||||
// Remove projectile
|
||||
g.projectiles = append(g.projectiles[:i-removed], g.projectiles[i-removed+1:]...)
|
||||
removed++
|
||||
// TODO Add bullet hole
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -771,40 +782,42 @@ UPDATEPROJECTILES:
|
|||
}
|
||||
}
|
||||
|
||||
// Spawn vampires.
|
||||
if g.tick%144 == 0 {
|
||||
spawnAmount := rand.Intn(26 + (g.tick / (144 * 3)))
|
||||
if len(g.level.creeps) < 500 {
|
||||
spawnAmount *= 4
|
||||
}
|
||||
if g.debugMode && spawnAmount > 0 {
|
||||
log.Printf("SPAWN %d VAMPIRES", spawnAmount)
|
||||
}
|
||||
for i := 0; i < spawnAmount; i++ {
|
||||
creepType := TypeVampire
|
||||
c := g.newCreep(creepType)
|
||||
if len(g.level.creeps) < maxCreeps {
|
||||
// Spawn vampires.
|
||||
if g.tick%144 == 0 {
|
||||
spawnAmount := rand.Intn(26 + (g.tick / (144 * 3)))
|
||||
if len(g.level.creeps) < 500 {
|
||||
spawnAmount *= 4
|
||||
}
|
||||
if g.debugMode && spawnAmount > 0 {
|
||||
log.Printf("SPAWN %d VAMPIRES", spawnAmount)
|
||||
}
|
||||
for i := 0; i < spawnAmount; i++ {
|
||||
creepType := TypeVampire
|
||||
c := g.newCreep(creepType)
|
||||
|
||||
g.level.creeps = append(g.level.creeps, c)
|
||||
g.level.creeps = append(g.level.creeps, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn bats.
|
||||
if g.tick%144 == 0 {
|
||||
spawnAmount := g.tick / 288
|
||||
if spawnAmount < 1 {
|
||||
spawnAmount = 1
|
||||
} else if spawnAmount > 12 {
|
||||
spawnAmount = 12
|
||||
}
|
||||
spawnAmount = rand.Intn(spawnAmount)
|
||||
if g.debugMode && spawnAmount > 0 {
|
||||
log.Printf("SPAWN %d BATS", spawnAmount)
|
||||
}
|
||||
for i := 0; i < spawnAmount; i++ {
|
||||
creepType := TypeBat
|
||||
c := g.newCreep(creepType)
|
||||
// Spawn bats.
|
||||
if g.tick%144 == 0 {
|
||||
spawnAmount := g.tick / 288
|
||||
if spawnAmount < 1 {
|
||||
spawnAmount = 1
|
||||
} else if spawnAmount > 12 {
|
||||
spawnAmount = 12
|
||||
}
|
||||
spawnAmount = rand.Intn(spawnAmount)
|
||||
if g.debugMode && spawnAmount > 0 {
|
||||
log.Printf("SPAWN %d BATS", spawnAmount)
|
||||
}
|
||||
for i := 0; i < spawnAmount; i++ {
|
||||
creepType := TypeBat
|
||||
c := g.newCreep(creepType)
|
||||
|
||||
g.level.creeps = append(g.level.creeps, c)
|
||||
g.level.creeps = append(g.level.creeps, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module code.rocketnine.space/tslocum/carotidartillery
|
|||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Meshiest/go-dungeon v0.0.0-20160809210039-1d1d1e7596b8
|
||||
github.com/hajimehoshi/ebiten/v2 v2.2.1
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||
golang.org/x/text v0.3.7
|
||||
|
|
2
go.sum
2
go.sum
|
@ -4,6 +4,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Meshiest/go-dungeon v0.0.0-20160809210039-1d1d1e7596b8 h1:nL4v6/vh9uBtMk4mZWhbdUOgisnpODxFjT2/h951GGU=
|
||||
github.com/Meshiest/go-dungeon v0.0.0-20160809210039-1d1d1e7596b8/go.mod h1:mQT0Ddn2ir5O4dIO3KS8EUKZZjiFO66nIF46cl9vrLs=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
|
|
169
level.go
169
level.go
|
@ -4,9 +4,12 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/Meshiest/go-dungeon/dungeon"
|
||||
)
|
||||
|
||||
const dungeonScale = 4
|
||||
|
||||
// Level represents a game level.
|
||||
type Level struct {
|
||||
w, h int
|
||||
|
@ -35,18 +38,27 @@ func (l *Level) Size() (width, height int) {
|
|||
return l.w, l.h
|
||||
}
|
||||
|
||||
func (l *Level) Clamp(x, y float64) (float64, float64) {
|
||||
if x < 0.3 {
|
||||
x = 0.3
|
||||
} else if x > float64(l.w)-1.3 {
|
||||
x = float64(l.w) - 1.3
|
||||
func (l *Level) isFloor(x float64, y float64, exact bool) bool {
|
||||
offsetA := .25
|
||||
offsetB := .75
|
||||
if exact {
|
||||
offsetA = 0
|
||||
offsetB = 0
|
||||
}
|
||||
if y < 0.4 {
|
||||
y = 0.4
|
||||
} else if y > float64(l.h)-1.8 {
|
||||
y = float64(l.h) - 1.8
|
||||
|
||||
t := l.Tile(int(x+offsetA), int(y+offsetA))
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
return x, y
|
||||
if !t.floor {
|
||||
return false
|
||||
}
|
||||
|
||||
t = l.Tile(int(x+offsetB), int(y+offsetB))
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
return t.floor
|
||||
}
|
||||
|
||||
func (l *Level) newSpawnLocation() (float64, float64) {
|
||||
|
@ -55,6 +67,10 @@ SPAWNLOCATION:
|
|||
x := float64(1 + rand.Intn(l.w-2))
|
||||
y := float64(1 + rand.Intn(l.h-2))
|
||||
|
||||
if !l.isFloor(x, y, false) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Too close to player.
|
||||
playerSafeSpace := 18.0
|
||||
dx, dy := deltaXY(x, y, l.player.x, l.player.y)
|
||||
|
@ -82,10 +98,10 @@ SPAWNLOCATION:
|
|||
|
||||
// NewLevel returns a new randomly generated Level.
|
||||
func NewLevel() (*Level, error) {
|
||||
// Create a 108x108 Level.
|
||||
// Create a 216x216 Level.
|
||||
l := &Level{
|
||||
w: 108,
|
||||
h: 108,
|
||||
w: 216,
|
||||
h: 216,
|
||||
tileSize: 32,
|
||||
}
|
||||
|
||||
|
@ -94,50 +110,111 @@ func NewLevel() (*Level, error) {
|
|||
return nil, fmt.Errorf("failed to load embedded spritesheet: %s", err)
|
||||
}
|
||||
|
||||
_ = sandstoneSS
|
||||
|
||||
// Generate a unique permutation each time.
|
||||
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
|
||||
|
||||
// Fill each tile with one or more sprites randomly.
|
||||
dungeon := dungeon.NewDungeon(l.w/dungeonScale, 13)
|
||||
dungeonFloor := 1
|
||||
l.tiles = make([][]*Tile, l.h)
|
||||
for y := 0; y < l.h; y++ {
|
||||
l.tiles[y] = make([]*Tile, l.w)
|
||||
for x := 0; x < l.w; x++ {
|
||||
t := &Tile{}
|
||||
t.AddSprite(sandstoneSS.FloorA)
|
||||
|
||||
val := r.Intn(1000)
|
||||
switch {
|
||||
case x == 0 && y == 0:
|
||||
t.AddSprite(sandstoneSS.WallTopLeft)
|
||||
case x == l.w-1 && y == 0:
|
||||
t.AddSprite(sandstoneSS.WallTopRight)
|
||||
case x == 0 && y == l.h-1:
|
||||
t.AddSprite(sandstoneSS.WallBottom)
|
||||
case x == l.w-1 && y == l.h-1:
|
||||
t.AddSprite(sandstoneSS.WallBottom)
|
||||
case y == 0:
|
||||
if x%(l.w/7) == 1 {
|
||||
t.AddSprite(sandstoneSS.WallPillar)
|
||||
if y < l.h-1 && dungeon.Grid[x/dungeonScale][y/dungeonScale] == dungeonFloor {
|
||||
if rand.Intn(13) == 0 {
|
||||
t.AddSprite(sandstoneSS.FloorC)
|
||||
} else {
|
||||
t.AddSprite(sandstoneSS.WallTop)
|
||||
t.AddSprite(sandstoneSS.FloorA)
|
||||
}
|
||||
case y == l.h-1:
|
||||
t.AddSprite(sandstoneSS.WallBottom)
|
||||
case x == 0:
|
||||
t.AddSprite(sandstoneSS.WallLeft)
|
||||
case x == l.w-1:
|
||||
t.AddSprite(sandstoneSS.WallRight)
|
||||
case val < 275:
|
||||
//t.AddSprite(sandstoneSS.FloorB)
|
||||
case val < 500:
|
||||
t.AddSprite(sandstoneSS.FloorC)
|
||||
t.floor = true
|
||||
}
|
||||
l.tiles[y][x] = t
|
||||
}
|
||||
}
|
||||
|
||||
neighbors := func(x, y int) [][2]int {
|
||||
return [][2]int{
|
||||
{x - 1, y - 1},
|
||||
{x, y - 1},
|
||||
{x + 1, y - 1},
|
||||
{x + 1, y},
|
||||
{x + 1, y + 1},
|
||||
{x, y + 1},
|
||||
{x - 1, y + 1},
|
||||
{x - 1, y},
|
||||
}
|
||||
}
|
||||
|
||||
floorTile := func(x, y int) bool {
|
||||
t := l.Tile(x, y)
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
return t.floor
|
||||
}
|
||||
|
||||
// Add walls.
|
||||
for x := 0; x < l.w; x++ {
|
||||
for y := 0; y < l.h; y++ {
|
||||
t := l.Tile(x, y)
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
if !t.floor {
|
||||
continue
|
||||
}
|
||||
for _, n := range neighbors(x, y) {
|
||||
nx, ny := n[0], n[1]
|
||||
neighbor := l.Tile(nx, ny)
|
||||
if neighbor == nil || neighbor.floor || neighbor.wall {
|
||||
continue
|
||||
}
|
||||
neighbor.wall = true
|
||||
|
||||
// From perspective of neighbor tile.
|
||||
bottom := floorTile(nx, ny+1)
|
||||
top := floorTile(nx, ny-1)
|
||||
right := floorTile(nx+1, ny)
|
||||
left := floorTile(nx-1, ny)
|
||||
topLeft := floorTile(nx-1, ny-1)
|
||||
topRight := floorTile(nx+1, ny-1)
|
||||
bottomLeft := floorTile(nx-1, ny+1)
|
||||
bottomRight := floorTile(nx+1, ny+1)
|
||||
|
||||
// Determine which wall sprite to belongs here.
|
||||
spriteTop := !top && bottom
|
||||
spriteLeft := (left || bottomLeft) && !right && !bottomRight && !bottom
|
||||
spriteRight := (right || bottomRight) && !left && !bottomLeft && !bottom
|
||||
spriteBottomRight := !topLeft && !top && topRight && !bottomLeft && !bottom && !bottomRight
|
||||
spriteBottomLeft := topLeft && !top && !topRight && !bottomLeft && !bottom && !bottomRight
|
||||
spriteBottom := top && !bottom
|
||||
|
||||
// Add wall sprite.
|
||||
switch {
|
||||
case spriteTop:
|
||||
if !bottomLeft || !bottomRight || left || right {
|
||||
neighbor.AddSprite(sandstoneSS.WallPillar)
|
||||
} else {
|
||||
neighbor.AddSprite(sandstoneSS.WallTop)
|
||||
}
|
||||
case spriteLeft:
|
||||
if spriteBottom {
|
||||
neighbor.AddSprite(sandstoneSS.WallBottom)
|
||||
}
|
||||
neighbor.AddSprite(sandstoneSS.WallLeft)
|
||||
case spriteRight:
|
||||
if spriteBottom {
|
||||
neighbor.AddSprite(sandstoneSS.WallBottom)
|
||||
}
|
||||
neighbor.AddSprite(sandstoneSS.WallRight)
|
||||
case spriteBottomLeft:
|
||||
neighbor.AddSprite(sandstoneSS.WallBottomLeft)
|
||||
case spriteBottomRight:
|
||||
neighbor.AddSprite(sandstoneSS.WallBottomRight)
|
||||
case spriteBottom:
|
||||
neighbor.AddSprite(sandstoneSS.WallBottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
|
|
3
main.go
3
main.go
|
@ -3,13 +3,12 @@ package main
|
|||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue