last_light/game/model/world_dungeon.go
2024-06-06 23:17:22 +03:00

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()
}