Skip to content

Commit

Permalink
fruit_density.png converter exe and script. Converts PNG to 12 channe…
Browse files Browse the repository at this point in the history
…ls, removes orphan pixels for non-existant foliage types, avoiding later errors with this file.
  • Loading branch information
Tresk7924 committed Jun 26, 2021
1 parent 75e665d commit ab00220
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 8 deletions.
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```
<File fileId="219" filename="mapDE/fruit_density.gdm"/>
```
to
```
<File fileId="219" filename="mapDE/fruit_density.png"/>
```
Find the `FoliageMultiLayer` line, e.g.:
```
<FoliageMultiLayer densityMapId="219" numChannels="10" numTypeIndexChannels="5" compressionChannels="5">
```
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:
```
<File fileId="219" filename="mapDE/fruit_density.png"/>
```
to
```
<File fileId="219" filename="mapDE/fruit_density.gdm"/>
```
Then save and exit.

Your map now supports 64 foliage types.

## Linking HorseExtension files

Expand Down
Binary file modified support/fruit_density_converter.exe
Binary file not shown.
66 changes: 59 additions & 7 deletions support/fruit_density_converter.py
Original file line number Diff line number Diff line change
@@ -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...")
Expand Down

0 comments on commit ab00220

Please sign in to comment.