diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README | 56 | ||||
-rw-r--r-- | animalfix.py | 237 |
3 files changed, 294 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5132458 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +pymclevel @@ -0,0 +1,56 @@ +mcanimalfix +=========== + +This tool will place animals in your Minecraft map, if there are no animals existing. +This can happen to old maps after upgrading to Minecraft >= Beta 1.8. + +It tries to simulate the standard Minecraft animal spawning mechanism. +Spawning wolves is a bit problematic, they only spawn in forest biomes, but it is +(IMHO) not possible to read out the used biome for a chunk. So this tool tries to figure out +where a forest could be (where are many trees?). + +WARNING! +-------- + +Before you do this, PLEASE backup your Map. I could not see any problems, but just in case... + +Requirements +------------ + +* Python 2.x (<http://www.python.org>) +* pymclevel (<https://github.com/codewarrior0/pymclevel>. Just place the pymclevel into the root directory of this tool) +* numpy (Needed by pymclevel. <http://numpy.scipy.org/>) +* wxPython (<http://wxpython.org/>. Make sure you use an Unicode-enabled version!) + +Usage +----- + +Launch `animalfix.py`, select the directory of your Minecraft map and click "Lets do it!". +Then you have to wait quite a long time. My map is ~ 50 MiB large and it took about 15 minutes to fix it (running on Fedora 14 with an Phenom II X4 965). + +Todo / Wishlist +--------------- + +* An "exefied" (you can not call this compiling for Python scripts IMHO...) version for Windows to make it easier for Windows users. + +License +------- + +NOTE: pymclevel is not my work. It was originally witten by codewarrior0. See <https://github.com/codewarrior0/pymclevel/blob/master/LICENSE.txt> for pymclevel's license. + +Now mcanimalfix' license: + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + 14 rue de Plaisance, 75014 Paris, France +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/animalfix.py b/animalfix.py new file mode 100644 index 0000000..020ac71 --- /dev/null +++ b/animalfix.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pymclevel import mclevel +import wx +import random + +def mkcoord(name, x,y,z): + coord = mclevel.TAG_List() + cx = mclevel.TAG_Double(name="", value=x) + cy = mclevel.TAG_Double(name="", value=y) + cz = mclevel.TAG_Double(name="", value=z) + coord.insert(0, cx) + coord.insert(1, cy) + coord.insert(2, cz) + coord.name = name + return coord + +def getsheepcol(): + r = random.randrange(0,10000) + if r < 8184: # 81.84 % chance + return 0 # White Wool + elif r < 8684: # 5 % chance + return 8 # Light Gray Wool + elif r < 9184: # 5 % chance + return 7 # Gray Wool + elif r < 9684: # 5 % chance + return 15 # Black Wool + elif r < 9984: # 3 % chance + return 12 # Brown Wool + else: # 0.16 chance + return 6 # Pink Wool + +def mkanimal(name, x,y,z): + if name == u"Chicken": + animal = mclevel.Entity.Create(u"Chicken") + animal["Health"] = mclevel.TAG_Short(name="Health", value=4) + elif name == u"Pig": + animal = mclevel.Entity.Create(u"Pig") + animal["Health"] = mclevel.TAG_Short(name="Health", value=10) + animal["Saddle"] = mclevel.TAG_Byte(name="Saddle", value=0) + elif name == u"Cow": + animal = mclevel.Entity.Create(u"Cow") + animal["Health"] = mclevel.TAG_Short(name="Health", value=10) + elif name == u"Sheep": + animal = mclevel.Entity.Create(u"Sheep") + animal["Health"] = mclevel.TAG_Short(name="Health", value=10) + animal["Color"] = mclevel.TAG_Byte(name="Color", value=getsheepcol()) + animal["Sheared"] = mclevel.TAG_Byte(name="Sheared", value=0) + elif name == u"Wolf": + animal = mclevel.Entity.Create(u"Wolf") + animal["Health"] = mclevel.TAG_Short(name="Health", value=8) + animal["Owner"] = mclevel.TAG_String(name="Owner", value=u"") + animal["Angry"] = mclevel.TAG_Byte(name="Angry", value=0) + animal["Sitting"] = mclevel.TAG_Byte(name="Angry", value=0) + else: + raise ValueError("Unknown animlal type '{}'.".format(name)) + + animal["Pos"] = mkcoord("Pos", x,y,z) + animal["Motion"] = mkcoord("Motion", 0,0,0) + animal["Rotation"] = mclevel.TAG_List(name="Rotation") + animal["Rotation"].insert(0, mclevel.TAG_Float(name="", value=0)) + animal["Rotation"].insert(1, mclevel.TAG_Float(name="", value=0)) + animal["Fire"] = mclevel.TAG_Short(name="Fire", value=-1) + animal["AttackTime"] = mclevel.TAG_Short(name="AttackTime", value=0) + animal["HurtTime"] = mclevel.TAG_Short(name="HurtTime", value=0) + animal["DeathTime"] = mclevel.TAG_Short(name="DeathTime", value=0) + animal["Air"] = mclevel.TAG_Short(name="Air", value=300) + animal["FallDistance"] = mclevel.TAG_Float(name="FallDistance", value=0) + animal["OnGround"] = mclevel.TAG_Byte(name="OnGround", value=0) + return animal + +class AnimalFixFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="AnimalFix", size=(400, 600)) + + self.mainpanel = wx.Panel(self, -1) + vbox = wx.BoxSizer(wx.VERTICAL) + + vbox.Add(wx.StaticText(self.mainpanel, +label="""This tool will place animals into your Minecraft map, if you do not have any. +This sometimes happens to an old map after updating to >Beta 1.8. +PLEASE! Make a backup of your map before apply this tool on your map!""", style=wx.ALIGN_CENTER), 0, wx.EXPAND | wx.ALL, 5) + + vbox.AddStretchSpacer(1) + + hbox_mapin = wx.BoxSizer(wx.HORIZONTAL) + hbox_mapin.Add(wx.StaticText(self.mainpanel, label="The directory of your old map:"), 0, wx.EXPAND | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5) + self.map_input = wx.DirPickerCtrl(self.mainpanel) + hbox_mapin.Add(self.map_input, 1, wx.EXPAND, 0) + vbox.Add(hbox_mapin, 0, wx.EXPAND | wx.ALL, 5) + + vbox.AddStretchSpacer(1) + + self.process_txt = wx.StaticText(self.mainpanel, label="") + vbox.Add(self.process_txt, 0, wx.EXPAND | wx.ALL, 5) + self.process_gauge = wx.Gauge(self.mainpanel, range=1000, style=wx.GA_HORIZONTAL | wx.GA_SMOOTH) + vbox.Add(self.process_gauge, 0, wx.EXPAND, wx.ALL, 5) + + vbox.AddStretchSpacer(1) + + self.letsgo = wx.Button(self.mainpanel, label="Lets do it!") + vbox.Add(self.letsgo, 0, wx.EXPAND | wx.ALL, 5) + + self.mainpanel.SetSizer(vbox) + vbox.Fit(self) + self.SetMinSize(vbox.GetMinSize()) + + # Events + self.Bind(wx.EVT_BUTTON, self.on_letsgo, id=self.letsgo.GetId()) + + def on_letsgo(self, evt): + try: + world = mclevel.fromFile(self.map_input.GetPath()) + except ValueError: + dialog = wx.MessageDialog(self, message="This is not a valid minecraft level!", caption="Could not load level", style=wx.OK | wx.ICON_ERROR) + dialog.ShowModal() + return True + except IOError: + dialog = wx.MessageDialog(self, message="Could not open directory!", caption="Could not load level", style=wx.OK | wx.ICON_ERROR) + dialog.ShowModal() + return True + + self.letsgo.Enable(False) + + overworld = world.getDimension(0) + + spawnon = [ + world.materials.Dirt.ID, + world.materials.Grass.ID, + world.materials.Gravel.ID, + world.materials.Farmland.ID, + world.materials.Snow.ID, + world.materials.Ice.ID, + world.materials.SnowLayer + ] + + treatasair = [ + world.materials.Air.ID, + world.materials.TallGrass.ID, + world.materials.Shrub.ID, + world.materials.Flower.ID, + world.materials.Rose.ID, + world.materials.RedMushroom.ID, + world.materials.BrownMushroom.ID, + ] + + animals = [u"Pig", u"Sheep", u"Chicken", u"Cow"] + allanimals = animals + [u"Wolf"] + + # Count Chunks + n_chunks = 0 + for _ in overworld.allChunks: + n_chunks += 1 + + i = 0 + nextanimalblock = random.randrange(1,11) + for cx, cz in overworld.allChunks: + i += 1 + chunk = overworld.getChunk(cx, cz) + + self.process_txt.SetLabel("{} / {}".format(i, n_chunks)) + self.process_gauge.SetValue(int((float(i) / float(n_chunks)) * 1000)) + self.Update() + + hasanimals = False + for entity in chunk.Entities: + if entity["id"].value in allanimals: + hasanimals = True + break + if hasanimals: + continue + + nextanimalblock -= 1 + if nextanimalblock > 0: + continue + + nextanimalblock = random.randrange(1,11) + + countleaves = 0 + possible_spawnpoints = [] + for x in xrange(16): + for y in xrange(16): + noleaves = True + for z in xrange(127, 0, -1): + blkmat = chunk.Blocks[x,y,z] + if blkmat not in treatasair: + if (blkmat == world.materials.Leaves.ID) and noleaves: + countleaves += 1 + noleaves = False + else: + if blkmat in spawnon: + if world.materials.Leaves.ID not in chunk.Blocks[x,y,z+1:z+3]: + # otherwise there would not be enough space... + possible_spawnpoints.append((x,y,z+1)) + break + + isforest = countleaves >= (16*16*0.45) # We assume the chunk belongs to a forest, if there are 45% leaves seen from above. + spawn_n = random.randrange(1,5) + if spawn_n > len(possible_spawnpoints): + spawn_n = len(possible_spawnpoints) + if spawn_n == 0: + continue + else: + random.shuffle(possible_spawnpoints) + current_animals = allanimals if isforest else animals + for x,z,y in possible_spawnpoints[0:spawn_n]: + x = cx * 16 + x + z = cz * 16 + z + chunk.Entities.append(mkanimal(random.choice(current_animals), x,y,z)) + chunk.chunkChanged() + + self.process_txt.SetLabel("Saving data. This can take \"some\" time. Please be patient...") + self.Update() + + world.generateLights(); + world.saveInPlace(); + + dialog = wx.MessageDialog(self, message="Finished", caption="New animals were placed successfully.", style=wx.OK | wx.ICON_INFORMATION) + dialog.ShowModal() + + self.process_gauge.SetValue(0) + self.process_txt.SetLabel("") + + self.letsgo.Enable(True) + return True + +class AnimalFixApplication(wx.App): + def OnInit(self): + my_frame = AnimalFixFrame() + my_frame.Show() + self.SetTopWindow(my_frame) + return True + +if __name__ == '__main__': + application = AnimalFixApplication() + application.MainLoop() |