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