summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README56
-rw-r--r--animalfix.py237
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
diff --git a/README b/README
new file mode 100644
index 0000000..e2ef448
--- /dev/null
+++ b/README
@@ -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()