1 package minecraft 2 3 import ( 4 "math/rand" 5 "time" 6 7 "vimagination.zapto.org/boolmap" 8 "vimagination.zapto.org/minecraft/nbt" 9 ) 10 11 var levelRequired = map[string]nbt.TagID{ 12 "LevelName": nbt.TagString, 13 "SpawnX": nbt.TagInt, 14 "SpawnY": nbt.TagInt, 15 "SpawnZ": nbt.TagInt, 16 } 17 18 // Level is the base type for minecraft data, all data for a minecraft level is 19 // either store in, or accessed from, this type. 20 type Level struct { 21 path Path 22 chunks map[uint64]*chunk 23 levelData nbt.Compound 24 changed bool 25 } 26 27 // NewLevel creates/Loads a minecraft level from the given path. 28 func NewLevel(location Path) (*Level, error) { 29 var ( 30 levelDat nbt.Tag 31 data nbt.Compound 32 changed bool 33 ) 34 35 levelDat, err := location.ReadLevelDat() 36 if err != nil { 37 return nil, err 38 } else if levelDat.TagID() == 0 { 39 levelDat = nbt.NewTag("", nbt.Compound{ 40 nbt.NewTag("Data", nbt.Compound{ 41 nbt.NewTag("version", nbt.Int(19133)), 42 nbt.NewTag("initialized", nbt.Byte(0)), 43 nbt.NewTag("LevelName", nbt.String("")), 44 nbt.NewTag("generatorName", nbt.String(DefaultGenerator)), 45 nbt.NewTag("generatorVersion", nbt.Int(0)), 46 nbt.NewTag("generatorOptions", nbt.String("0")), 47 nbt.NewTag("RandomSeed", nbt.Long(rand.New(rand.NewSource(time.Now().Unix())).Int63())), 48 nbt.NewTag("MapFeatures", nbt.Byte(1)), 49 nbt.NewTag("LastPlayed", nbt.Long(time.Now().Unix()*1000)), 50 nbt.NewTag("SizeOnDisk", nbt.Long(0)), 51 nbt.NewTag("allowCommands", nbt.Byte(0)), 52 nbt.NewTag("hardcore", nbt.Byte(0)), 53 nbt.NewTag("GameType", nbt.Int(Survival)), 54 nbt.NewTag("Time", nbt.Long(0)), 55 nbt.NewTag("DayTime", nbt.Long(0)), 56 nbt.NewTag("SpawnX", nbt.Int(0)), 57 nbt.NewTag("SpawnY", nbt.Int(0)), 58 nbt.NewTag("SpawnZ", nbt.Int(0)), 59 nbt.NewTag("BorderCenterX", nbt.Double(0)), 60 nbt.NewTag("BorderCenterZ", nbt.Double(0)), 61 nbt.NewTag("BorderSize", nbt.Double(60000000)), 62 nbt.NewTag("BorderSafeZone", nbt.Double(5)), 63 nbt.NewTag("BorderWarningTime", nbt.Double(15)), 64 nbt.NewTag("BorderSizeLerpTarget", nbt.Double(60000000)), 65 nbt.NewTag("BorderSizeLerpTime", nbt.Long(0)), 66 nbt.NewTag("BorderDamagePerBlock", nbt.Double(0.2)), 67 nbt.NewTag("raining", nbt.Byte(0)), 68 nbt.NewTag("rainTime", nbt.Int(0)), 69 nbt.NewTag("thundering", nbt.Byte(0)), 70 nbt.NewTag("thunderTime", nbt.Int(0)), 71 nbt.NewTag("clearWeatherTime", nbt.Int(0)), 72 nbt.NewTag("GameRules", nbt.Compound{ 73 nbt.NewTag("commandBlockOutput", nbt.String("True")), 74 nbt.NewTag("doDaylightCycle", nbt.String("True")), 75 nbt.NewTag("doFireTick", nbt.String("True")), 76 nbt.NewTag("doMobLoot", nbt.String("True")), 77 nbt.NewTag("doMobSpawning", nbt.String("True")), 78 nbt.NewTag("doTileDrops", nbt.String("True")), 79 nbt.NewTag("keepInventory", nbt.String("False")), 80 nbt.NewTag("logAdminCommands", nbt.String("True")), 81 nbt.NewTag("mobGriefing", nbt.String("True")), 82 nbt.NewTag("naturalRegeneration", nbt.String("True")), 83 nbt.NewTag("randomTickSpeed", nbt.String("3")), 84 nbt.NewTag("sendCommandFeedback", nbt.String("True")), 85 nbt.NewTag("showDeathMessages", nbt.String("True")), 86 }), 87 }), 88 }) 89 90 changed = true 91 } 92 93 if levelDat.TagID() != nbt.TagCompound { 94 return nil, WrongTypeError{"[BASE]", nbt.TagCompound, levelDat.TagID()} 95 } else if d := levelDat.Data().(nbt.Compound).Get("Data"); d.TagID() != 0 { 96 if d.TagID() == nbt.TagCompound { 97 data = d.Data().(nbt.Compound) 98 } else { 99 return nil, WrongTypeError{"Data", nbt.TagCompound, d.TagID()} 100 } 101 } else { 102 return nil, MissingTagError{"Data"} 103 } 104 105 for name, tagType := range levelRequired { 106 if x := data.Get(name); x.TagID() == 0 { 107 return nil, MissingTagError{name} 108 } else if x.TagID() != tagType { 109 return nil, WrongTypeError{name, tagType, x.TagID()} 110 } 111 } 112 113 return &Level{ 114 location, 115 make(map[uint64]*chunk), 116 levelDat.Data().(nbt.Compound).Get("Data").Data().(nbt.Compound), 117 changed, 118 }, nil 119 } 120 121 // GetBlock gets the block at coordinates x, y, z. 122 func (l *Level) GetBlock(x, y, z int32) (Block, error) { 123 if y < 0 { 124 return Block{}, nil 125 } 126 127 c, err := l.getChunk(x, z, false) 128 if err != nil { 129 return Block{}, err 130 } else if c == nil { 131 return Block{}, nil 132 } 133 134 return c.GetBlock(x, y, z), nil 135 } 136 137 // SetBlock sets the block at coordinates x, y, z. Also processes any lighting updates if applicable. 138 func (l *Level) SetBlock(x, y, z int32, block Block) error { 139 if y < 0 { 140 y = 0 141 } 142 143 var ( 144 c *chunk 145 err error 146 ) 147 148 for mx := x - 1; mx <= x+1; mx++ { 149 for mz := z - 1; mz <= z+1; mz++ { 150 if c, err = l.getChunk(mx, mz, true); err != nil { 151 return err 152 } 153 154 for my := y + 16; my >= 0; my -= 16 { 155 if c.createSection(my) { 156 break 157 } 158 } 159 } 160 } 161 162 c, _ = l.getChunk(x, z, false) 163 opacity := c.GetOpacity(x, y, z) 164 165 c.SetBlock(x, y, z, block) 166 167 if block.Opacity() != opacity { 168 if err = l.genLighting(x, y, z, true, block.Opacity() > opacity, 0); err != nil { 169 return err 170 } 171 } 172 173 if bl := c.GetBlockLight(x, y, z); block.Light() != bl || block.Opacity() != opacity { 174 if err = l.genLighting(x, y, z, false, block.Light() < bl, block.Light()); err != nil { 175 return err 176 } 177 } 178 179 return nil 180 } 181 182 type lightCoords struct { 183 x, y, z int32 184 lightLevel uint8 185 } 186 187 func (l *Level) genLighting(x, y, z int32, skyLight, darker bool, source uint8) error { 188 var ( 189 getLight func(*chunk, int32, int32, int32) uint8 190 setLight func(*chunk, int32, int32, int32, uint8) 191 ) 192 193 if skyLight { 194 getLight = (*chunk).GetSkyLight 195 setLight = (*chunk).SetSkyLight 196 } else { 197 getLight = (*chunk).GetBlockLight 198 setLight = (*chunk).SetBlockLight 199 } 200 201 c, err := l.getChunk(x, z, false) 202 if err != nil { 203 return err 204 } else if c == nil { 205 return nil 206 } 207 208 list := make([]*lightCoords, 1) 209 list[0] = &lightCoords{x, y, z, getLight(c, x, y, z)} 210 changed := boolmap.NewMap() 211 212 changed.SetBool((uint64(y)<<10)|(uint64(z&31)<<5)|uint64(x&31), true) 213 214 if darker { // reset lighting on all blocks affected by the changed one (only applies if darker) 215 setLight(c, x, y, z, 0) 216 217 for i := 0; i < len(list); i++ { 218 for _, s := range surroundingBlocks(list[i].x, list[i].y, list[i].z) { 219 mx, my, mz := s[0], s[1], s[2] 220 pos := (uint64(my) << 10) | (uint64(mz&31) << 5) | uint64(mx&31) 221 222 if changed.GetBool(pos) { 223 continue 224 } 225 226 if c, err = l.getChunk(mx, mz, false); err != nil { 227 return err 228 } else if c == nil { 229 continue 230 } else if ys := my >> 4; my < 16 && c.sections[ys] == nil { 231 changed.SetBool(pos, true) 232 233 continue 234 } 235 236 shouldBe := list[i].lightLevel 237 opacity := c.GetOpacity(mx, my, mz) 238 239 if opacity > shouldBe { 240 shouldBe = 0 241 } else { 242 shouldBe -= opacity 243 } 244 245 if thisLight := getLight(c, mx, my, mz); thisLight == shouldBe && shouldBe != 0 || (skyLight && thisLight == 15 && my < c.GetHeight(mx, mz)) { 246 list = append(list, &lightCoords{mx, my, mz, thisLight}) 247 248 changed.SetBool(pos, true) 249 250 if thisLight > 0 { 251 setLight(c, mx, my, mz, 0) 252 } 253 } 254 } 255 } 256 } // end lighting reset 257 258 if source > 0 { // If this is the source of light 259 c, _ = l.getChunk(x, z, false) 260 list = list[1:] 261 262 c.SetBlockLight(x, y, z, source) 263 264 for _, s := range surroundingBlocks(x, y, z) { 265 mx, my, mz := s[0], s[1], s[2] 266 pos := (uint64(my) << 10) | (uint64(mz&31) << 5) | uint64(mx&31) 267 268 if changed.GetBool(pos) { 269 continue 270 } 271 272 if c, err = l.getChunk(mx, mz, false); err != nil { 273 return err 274 } else if c == nil { 275 continue 276 } else if ys := my >> 4; my < 16 && c.sections[ys] == nil { 277 changed.SetBool(pos, true) 278 279 continue 280 } 281 282 if thisLight := getLight(c, mx, my, mz); thisLight < source { 283 list = append(list, &lightCoords{mx, my, mz, thisLight}) 284 285 changed.SetBool(pos, true) 286 } 287 } 288 } 289 290 for ; len(list) > 0; list = list[1:] { 291 mx, my, mz := list[0].x, list[0].y, list[0].z 292 newLight := uint8(0) 293 c, _ = l.getChunk(mx, mz, false) 294 295 changed.SetBool((uint64(my)<<10)|(uint64(mz&31)<<5)|uint64(mx&31), false) 296 297 if skyLight && my >= c.GetHeight(mx, mz) { // Determine correct light level... 298 newLight = 15 299 } else if opacity := c.GetOpacity(mx, my, mz); opacity == 15 { 300 newLight = 0 301 } else { 302 var d *chunk 303 304 for _, s := range surroundingBlocks(mx, my, mz) { 305 nx, ny, nz := s[0], s[1], s[2] 306 307 if d, err = l.getChunk(nx, nz, false); err != nil { 308 return err 309 } else if d == nil { 310 continue 311 } 312 313 curr := getLight(d, nx, ny, nz) 314 if curr < opacity { 315 continue 316 } 317 318 curr -= opacity 319 if curr > newLight { 320 newLight = curr 321 } 322 } 323 } // ...end determining light level 324 325 setLight(c, mx, my, mz, newLight) 326 327 if newLight > list[0].lightLevel || (darker && newLight == list[0].lightLevel) { 328 for _, s := range surroundingBlocks(mx, my, mz) { 329 mx, my, mz = s[0], s[1], s[2] 330 pos := (uint64(my) << 10) | (uint64(mz&31) << 5) | uint64(mx&31) 331 332 if changed.GetBool(pos) { 333 continue 334 } 335 336 if c, err = l.getChunk(mx, mz, false); err != nil { 337 return err 338 } else if c == nil { 339 continue 340 } else if ys := my >> 4; ys < 16 && c.sections[ys] == nil { 341 changed.SetBool(pos, true) 342 343 continue 344 } 345 346 if thisLight := getLight(c, mx, my, mz); thisLight < newLight { 347 list = append(list, &lightCoords{mx, my, mz, thisLight}) 348 349 changed.SetBool(pos, true) 350 } 351 } 352 } 353 } 354 355 return nil 356 } 357 358 // GetBiome returns the biome for the column x, z. 359 func (l *Level) GetBiome(x, z int32) (Biome, error) { 360 c, err := l.getChunk(x, z, false) 361 if err != nil { 362 return AutoBiome, err 363 } else if c == nil { 364 return Plains, nil 365 } 366 367 return c.GetBiome(x, z), nil 368 } 369 370 // SetBiome sets the biome for the column x, z. 371 func (l *Level) SetBiome(x, z int32, biome Biome) error { 372 c, err := l.getChunk(x, z, true) 373 if err != nil { 374 return err 375 } 376 377 c.SetBiome(x, z, biome) 378 379 return nil 380 } 381 382 // GetHeight returns the y coordinate for the highest non-transparent block at column x, z. 383 func (l *Level) GetHeight(x, z int32) (int32, error) { 384 c, err := l.getChunk(x, z, false) 385 if err != nil || c == nil { 386 return 0, err 387 } 388 389 return c.GetHeight(x, z), nil 390 } 391 392 func (l *Level) getChunk(x, z int32, create bool) (*chunk, error) { 393 x >>= 4 394 z >>= 4 395 pos := uint64(z)<<32 | uint64(uint32(x)) 396 397 if l.chunks[pos] == nil { 398 chunkData, err := l.path.GetChunk(x, z) 399 if err != nil { 400 return nil, err 401 } 402 403 if chunkData.TagID() != 0 { 404 chunk, err := newChunk(x, z, chunkData) 405 if err != nil { 406 return nil, err 407 } 408 409 l.chunks[pos] = chunk 410 } else if create { 411 l.chunks[pos], _ = newChunk(x, z, nbt.Tag{}) 412 } 413 } 414 415 return l.chunks[pos], nil 416 } 417 418 // Save saves all open chunks, but does not close them. 419 func (l *Level) Save() error { 420 if l.changed { 421 if err := l.path.WriteLevelDat(nbt.NewTag("", nbt.Compound{nbt.NewTag("Data", l.levelData)})); err != nil { 422 return err 423 } 424 425 l.changed = false 426 } 427 428 toSave := make([]nbt.Tag, 0, len(l.chunks)) 429 430 for _, c := range l.chunks { 431 toSave = append(toSave, c.GetNBT()) 432 } 433 434 if len(toSave) > 0 { 435 return l.path.SetChunk(toSave...) // check multi-error 436 } 437 438 return nil 439 } 440 441 // Close closes all open chunks, but does not save them. 442 func (l *Level) Close() { 443 l.changed = false 444 l.chunks = make(map[uint64]*chunk) 445 } 446 447 func surroundingBlocks(x, y, z int32) [][3]int32 { 448 sB := [6][3]int32{ 449 {x, y - 1, z}, 450 {x - 1, y, z}, 451 {x + 1, y, z}, 452 {x, y, z - 1}, 453 {x, y, z + 1}, 454 {x, y + 1, z}, 455 } 456 457 if y == 0 { 458 return sB[1:] 459 } 460 461 if y == 255 { 462 return sB[:5] 463 } 464 465 return sB[:] 466 } 467