mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
328 lines
6.2 KiB
Go
328 lines
6.2 KiB
Go
package model
|
|
|
|
import (
|
|
"math/rand"
|
|
"mvvasilev/last_light/engine"
|
|
"slices"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type DungeonType int
|
|
|
|
const (
|
|
DungeonTypeBSP DungeonType = iota
|
|
DungeonTypeCaverns
|
|
DungeonTypeMine
|
|
DungeonTypeUndercity
|
|
)
|
|
|
|
func randomDungeonType() DungeonType {
|
|
return DungeonType(rand.Intn(4))
|
|
}
|
|
|
|
type Dungeon struct {
|
|
levels []*DungeonLevel
|
|
|
|
current int
|
|
}
|
|
|
|
func CreateDungeon(width, height int, depth int) *Dungeon {
|
|
levels := make([]*DungeonLevel, 0, depth)
|
|
|
|
for range depth {
|
|
levels = append(levels, CreateDungeonLevel(width, height, randomDungeonType()))
|
|
}
|
|
|
|
return &Dungeon{
|
|
levels: levels,
|
|
current: 0,
|
|
}
|
|
}
|
|
|
|
func (d *Dungeon) CurrentLevel() *DungeonLevel {
|
|
return d.levels[d.current]
|
|
}
|
|
|
|
func (d *Dungeon) MoveToNextLevel() (moved bool) {
|
|
if !d.HasNextLevel() {
|
|
return false
|
|
}
|
|
|
|
d.current++
|
|
|
|
return true
|
|
}
|
|
|
|
func (d *Dungeon) MoveToPreviousLevel() (moved bool) {
|
|
if !d.HasPreviousLevel() {
|
|
return false
|
|
}
|
|
|
|
d.current--
|
|
|
|
return true
|
|
}
|
|
|
|
func (d *Dungeon) NextLevel() *DungeonLevel {
|
|
if !d.HasNextLevel() {
|
|
return nil
|
|
}
|
|
|
|
return d.levels[d.current+1]
|
|
}
|
|
|
|
func (d *Dungeon) PreviousLevel() *DungeonLevel {
|
|
if !d.HasPreviousLevel() {
|
|
return nil
|
|
}
|
|
|
|
return d.levels[d.current-1]
|
|
}
|
|
|
|
func (d *Dungeon) HasPreviousLevel() bool {
|
|
return d.current-1 >= 0
|
|
}
|
|
|
|
func (d *Dungeon) HasNextLevel() bool {
|
|
return d.current+1 < len(d.levels)
|
|
}
|
|
|
|
type DungeonLevel struct {
|
|
ground Map_V2
|
|
entitiesByPosition map[engine.Position]Entity_V2
|
|
entities map[uuid.UUID]Entity_V2
|
|
}
|
|
|
|
func CreateDungeonLevel(width, height int, dungeonType DungeonType) (dLevel *DungeonLevel) {
|
|
|
|
genTable := CreateLootTable()
|
|
|
|
genTable.Add(1, func() Item_V2 {
|
|
return Item_HealthPotion()
|
|
})
|
|
|
|
itemPool := []Item_V2{
|
|
Item_Bow(),
|
|
Item_Longsword(),
|
|
Item_Club(),
|
|
Item_Dagger(),
|
|
Item_Handaxe(),
|
|
Item_Javelin(),
|
|
Item_LightHammer(),
|
|
Item_Mace(),
|
|
Item_Quarterstaff(),
|
|
Item_Sickle(),
|
|
Item_Spear(),
|
|
}
|
|
|
|
genTable.Add(1, func() Item_V2 {
|
|
item := itemPool[rand.Intn(len(itemPool))]
|
|
|
|
rarities := []ItemRarity{
|
|
ItemRarity_Common,
|
|
ItemRarity_Uncommon,
|
|
ItemRarity_Rare,
|
|
ItemRarity_Epic,
|
|
ItemRarity_Legendary,
|
|
}
|
|
|
|
return GenerateItemOfTypeAndRarity(item, rarities[rand.Intn(len(rarities))])
|
|
})
|
|
|
|
var groundLevel Map_V2
|
|
|
|
switch dungeonType {
|
|
case DungeonTypeBSP:
|
|
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
|
default:
|
|
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
|
}
|
|
|
|
dLevel = &DungeonLevel{
|
|
ground: groundLevel,
|
|
entities: map[uuid.UUID]Entity_V2{},
|
|
entitiesByPosition: map[engine.Position]Entity_V2{},
|
|
}
|
|
|
|
if groundLevel.Rooms() == nil {
|
|
return dLevel
|
|
}
|
|
|
|
forbiddenItemPositions := make([]engine.Position, 0)
|
|
|
|
if groundLevel.NextLevelStaircase() != nil {
|
|
forbiddenItemPositions = append(forbiddenItemPositions, groundLevel.NextLevelStaircase().Position)
|
|
}
|
|
|
|
if groundLevel.PreviousLevelStaircase() != nil {
|
|
forbiddenItemPositions = append(forbiddenItemPositions, groundLevel.PreviousLevelStaircase().Position)
|
|
}
|
|
|
|
if groundLevel.PlayerSpawnPoint() != nil {
|
|
forbiddenItemPositions = append(forbiddenItemPositions, groundLevel.PreviousLevelStaircase().Position)
|
|
}
|
|
|
|
items := SpawnItems(groundLevel.Rooms().Rooms, 0.01, genTable, forbiddenItemPositions)
|
|
|
|
for pos, it := range items {
|
|
tile := Map_TileAt(groundLevel, pos.X(), pos.Y())
|
|
|
|
if !tile.Passable() {
|
|
continue
|
|
}
|
|
|
|
Map_SetTileAt(
|
|
groundLevel,
|
|
pos.X(),
|
|
pos.Y(),
|
|
CreateTileFromPrototype(tile, Tile_WithItem(it)),
|
|
)
|
|
}
|
|
|
|
return dLevel
|
|
}
|
|
|
|
func SpawnItems(spawnableAreas []engine.BoundingBox, maxItemRatio float32, genTable *LootTable, forbiddenPositions []engine.Position) map[engine.Position]Item_V2 {
|
|
rooms := spawnableAreas
|
|
|
|
itemLocations := make(map[engine.Position]Item_V2, 0)
|
|
|
|
for _, r := range rooms {
|
|
maxItems := int(maxItemRatio * float32(r.Size().Area()))
|
|
|
|
if maxItems < 1 {
|
|
continue
|
|
}
|
|
|
|
numItems := rand.Intn(maxItems)
|
|
|
|
for range numItems {
|
|
item := genTable.Generate()
|
|
|
|
if item == nil {
|
|
continue
|
|
}
|
|
|
|
pos := engine.PositionAt(
|
|
engine.RandInt(r.Position().X()+1, r.Position().X()+r.Size().Width()-1),
|
|
engine.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
|
|
)
|
|
|
|
if slices.Contains(forbiddenPositions, pos) {
|
|
continue
|
|
}
|
|
|
|
itemLocations[pos] = item
|
|
}
|
|
}
|
|
|
|
return itemLocations
|
|
}
|
|
|
|
func (d *DungeonLevel) Ground() Map_V2 {
|
|
return d.ground
|
|
}
|
|
|
|
func (d *DungeonLevel) DropEntity(uuid uuid.UUID) {
|
|
ent := d.entities[uuid]
|
|
|
|
if ent != nil {
|
|
delete(d.entitiesByPosition, ent.Positioned().Position)
|
|
}
|
|
|
|
delete(d.entities, uuid)
|
|
}
|
|
|
|
func (d *DungeonLevel) AddEntity(entity Entity_V2) {
|
|
d.entities[entity.UniqueId()] = entity
|
|
|
|
if entity.Positioned() != nil {
|
|
d.entitiesByPosition[entity.Positioned().Position] = entity
|
|
}
|
|
}
|
|
|
|
func (d *DungeonLevel) MoveEntityTo(uuid uuid.UUID, x, y int) {
|
|
ent := d.entities[uuid]
|
|
|
|
if ent == nil || ent.Positioned() == nil {
|
|
return
|
|
}
|
|
|
|
d.RemoveEntityAt(ent.Positioned().Position.XY())
|
|
|
|
ent.Positioned().Position = engine.PositionAt(x, y)
|
|
|
|
d.entitiesByPosition[ent.Positioned().Position] = ent
|
|
}
|
|
|
|
func (d *DungeonLevel) RemoveEntityAt(x, y int) {
|
|
delete(d.entitiesByPosition, engine.PositionAt(x, y))
|
|
}
|
|
|
|
func (d *DungeonLevel) RemoveItemAt(x, y int) (item Item_V2) {
|
|
if !Map_IsInBounds(d.ground, x, y) {
|
|
return nil
|
|
}
|
|
|
|
tile := Map_TileAt(d.ground, x, y)
|
|
|
|
if tile.Item() == nil {
|
|
return nil
|
|
}
|
|
|
|
item = tile.Item().Item
|
|
|
|
tile.RemoveItem()
|
|
|
|
return
|
|
}
|
|
|
|
func (d *DungeonLevel) SetItemAt(x, y int, it Item_V2) (success bool) {
|
|
if !d.TileAt(x, y).Passable() {
|
|
return false
|
|
}
|
|
|
|
tile := d.TileAt(x, y)
|
|
|
|
tile.WithItem(it)
|
|
|
|
return true
|
|
}
|
|
|
|
func (d *DungeonLevel) TileAt(x, y int) Tile_V2 {
|
|
entity := d.entitiesByPosition[engine.PositionAt(x, y)]
|
|
tile := Map_TileAt(d.ground, x, y)
|
|
|
|
if entity != nil {
|
|
return CreateTileFromPrototype(tile, Tile_WithEntity(entity))
|
|
}
|
|
|
|
return tile
|
|
}
|
|
|
|
func (d *DungeonLevel) IsTilePassable(x, y int) bool {
|
|
if !Map_IsInBounds(d.ground, x, y) {
|
|
return false
|
|
}
|
|
|
|
tile := d.TileAt(x, y)
|
|
|
|
if tile.Entity() != nil {
|
|
return false
|
|
}
|
|
|
|
return tile.Passable()
|
|
}
|
|
|
|
func (d *DungeonLevel) EntityAt(x, y int) (e Entity_V2) {
|
|
return d.entitiesByPosition[engine.PositionAt(x, y)]
|
|
}
|
|
|
|
func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool {
|
|
if !Map_IsInBounds(d.ground, x, y) {
|
|
return false
|
|
}
|
|
|
|
return Map_TileAt(d.ground, x, y).Opaque()
|
|
}
|