diff --git a/README.md b/README.md
index 4df2c33..326b0ef 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,39 @@ By default FS19 maps don't support enough fruitTypes, so we have to fix that fir
Like the heightTypes and MTA support, find `fruit_density.gdm`, convert it to a PNG with the GRLE converter, make sure the new `fruit_density.png` file is in the same directory as the original GDM file, and DELETE the original `fruit_density.gdm`.
-Some maps have bugs resulting from what we do next, because we're changing what bits in the fruit density image mean. I've had grass patches become withered potatoes, and soybeans instead of flowers in people's gardens. I've included an image converter to deal with this at support/fruit_density_converter.exe (adapted from work on the [LS-ModCompany forums](https://ls-modcompany.com/forum/thread/8049-limit-f%C3%BCr-fruchtsorten-in-beliebiger-map-erh%C3%B6hen/?postID=93808)). It works the same as the GRLE converter, so drag the new `fruit_density.png` file onto the executable and you'll get `fruit_density_new.png`. Delete the original `fruit_density.png` and rename `fruit_density_new.png` to `fruit_density.png` to replace it.
+Some maps have bugs resulting from what we do next, because we're changing what bits in the fruit density image mean. I've had grass patches become withered potatoes, and soybeans instead of flowers in people's gardens. I've included an image converter to deal with this at `support/fruit_density_converter.exe` (adapted from work on the [LS-ModCompany forums](https://ls-modcompany.com/forum/thread/8049-limit-f%C3%BCr-fruchtsorten-in-beliebiger-map-erh%C3%B6hen/?postID=93808)). There are also some problems with maps that have errors in their fruit_density.gdm files (e.g. No Man's Land has entries for foliage that doesn't exist). It works a little differently because it needs to examine the map. Drag your map's modDesc.xml onto `support/fruit_density_converter.exe` and it will do the work for you, creating `fruit_density_new.png`. Delete `fruit_density.png`, rename `fruit_density_new.png` to `fruit_density.png`. There's also a Python script version of the converter if you're comfortable with that.
+
+Open your map's i3d in Giants Editor, and change:
+
+```
+
+```
+to
+```
+
+```
+Find the `FoliageMultiLayer` line, e.g.:
+```
+
+```
+change:
+- `numChannels` to `12`
+- `numTypeIndexChannels` to `6`
+- `compressionChannels` to `6`
+
+Save the i3d and open it with Giants Editor, checking for errors.
+
+Go back in and change:
+```
+
+```
+to
+```
+
+```
+Then save and exit.
+
+Your map now supports 64 foliage types.
## Linking HorseExtension files
diff --git a/support/fruit_density_converter.exe b/support/fruit_density_converter.exe
index 9851439..5d87518 100644
Binary files a/support/fruit_density_converter.exe and b/support/fruit_density_converter.exe differ
diff --git a/support/fruit_density_converter.py b/support/fruit_density_converter.py
index 897b92e..f7656b9 100644
--- a/support/fruit_density_converter.py
+++ b/support/fruit_density_converter.py
@@ -1,21 +1,73 @@
from PIL import Image
import sys
from os import path
+import xml.etree.ElementTree as ElTree
-def update_pixel(p):
+def update_pixel(p, max_foliage_index):
+ # Get combined red (p[0]) and green p[1] values as combined 16 bit value [8 bits green, 8 bits red]
channel = p[0] + (256 * p[1])
- state = channel // 32
+ # First 5 red bits are crop ID, little-endian, representing 0-31
index = channel % 32
+ # Invalid pixels set to black (where foliage channel unused in map
+ if index > max_foliage_index:
+ return (0,0,0)
+ # Next 5 bits are crop growth stage (last 3 red bits + first 2 green)
+ state = channel // 32
+ # Multiply state by 64 (6 bit offset) to leave a 6 bit window for crop indices
new_channel = (state * 64) + index
+ # Recombine value into 8 red bits (lower values), 8 green bits (higher values), and blue (unused)
return (new_channel % 256, new_channel // 256, 0)
-img_filepath = sys.argv[1]
-img = Image.open(img_filepath)
-print(f"Processing {img_filepath}")
+def usage():
+ print("Map directory or modDesc.xml file path required as first argument\n")
+ print("Try dragging your map's modDesc.xml onto fruit_density_converter.exe\n")
+ print("Press any key to continue...")
+ input()
+ exit()
+
+# Get the map's base directory from command line arguments
+try:
+ map_path = sys.argv[1]
+ if path.isdir(map_path):
+ map_dir = map_path
+ else:
+ map_dir = path.dirname(map_path)
+except IndexError:
+ usage()
+
+try:
+ # Inspecting the map. First we get the modDesc.xml
+ mod_desc_path = path.join(map_dir, 'modDesc.xml')
+ print(f"Parsing modDesc at {mod_desc_path}")
+ mod_desc = ElTree.parse(mod_desc_path)
+ # Get the map's XML path from the modDesc.xml
+ map_xml_path = path.join(map_dir, mod_desc.getroot().find('maps').find('map').attrib["configFilename"])
+ print(f"Parsing map config XML at {map_xml_path}")
+ map_xml = ElTree.parse(map_xml_path)
+ # Get the map's i3d path from the config XML
+ map_i3d_path = path.join(map_dir, map_xml.getroot().find('filename').text)
+ print(f"Parsing map i3d file at {map_i3d_path}")
+ map_i3d = ElTree.parse(map_i3d_path)
+ # Count the number of foliage types in the map so we can identify invalid pixels
+ foliage_type_count = len(map_i3d.getroot().find(
+ "./Scene/TerrainTransformGroup/Layers/FoliageMultiLayer"
+ ).findall("FoliageType"))
+ print(f"Number of foliage indices used: {foliage_type_count}")
+ # Find the fruit_density file path from the i3d File listings
+ for file_entry in map_i3d.getroot().findall("./Files/File"):
+ filename = file_entry.get("filename", "")
+ if filename.endswith("fruit_density.gdm") or filename.endswith("fruit_density.png"):
+ fruit_density_path = path.join(path.dirname(path.join(path.dirname(map_i3d_path), filename)), "fruit_density.png")
+ break
+except ElTree.ParseError:
+ usage()
+
+print(f"Processing fruit_density PNG at {fruit_density_path}")
+img = Image.open(fruit_density_path)
list_of_pixels = list(img.getdata())
-new_list_of_pixels = list(map(update_pixel, list_of_pixels))
+new_list_of_pixels = [update_pixel(p, (foliage_type_count - 1)) for p in list_of_pixels]
img.putdata(new_list_of_pixels)
-converted_img_path = path.join(path.dirname(img_filepath), "fruit_density_new.png")
+converted_img_path = path.join(path.dirname(fruit_density_path), "fruit_density_new.png")
img.save(converted_img_path)
print(f"Saved new image at: {converted_img_path}")
print("Press any key to continue...")