Files
openttd-cmclient/grf/alpine/alpine.py
2024-02-04 03:52:22 +05:30

414 lines
13 KiB
Python

from pathlib import Path
import numpy as np
import spectra
from PIL import Image
import grf
OGFX_PATH = Path('OpenGFX2_Classic-0.1')
OGFX_BASE_PATH = OGFX_PATH / 'ogfx21_base_8.grf'
THIS_FILE = grf.PythonFile(__file__)
class GRFFile(grf.LoadedResourceFile):
def __init__(self, path):
self.path = path
self.real_sprites = None
self.load()
def load(self):
if self.real_sprites is not None:
return
print(f'Decompiling {self.path}...')
self.context = grf.decompile.ParsingContext()
self.f = open(self.path, 'rb')
self.gen, self.container, self.real_sprites = grf.decompile.read(self.f, self.context)
def unload(self):
self.context = None
self.gen = None
self.container = None
self.real_sprites = None
self.f.close()
def get_sprite_data(self, sprite_id):
s = self.real_sprites[sprite_id + 1]
assert len(s) == 1, len(s)
s = s[0]
self.f.seek(s.offset)
data, _ = grf.decompile.decode_sprite(self.f, s, self.container)
return data
def get_sprite(self, sprite_id):
s = self.real_sprites[sprite_id + 1]
assert len(s) == 1, len(s)
s = s[0]
return GRFSprite(self, s.type, s.bpp, sprite_id, w=s.width, h=s.height, xofs=s.xofs, yofs=s.yofs, zoom=s.zoom, crop=False)
class GRFSprite(grf.Sprite):
def __init__(self, file, type, grf_bpp, sprite_id, *args, **kw):
super().__init__(*args, **kw)
self.file = file
self.type = type
self.grf_bpp = grf_bpp
self.sprite_id = sprite_id
bpp = grf_bpp
if self.type & 0x04:
bpp -= 1
if bpp == 3:
self.bpp = grf.BPP_24
elif bpp == 4:
self.bpp = grf.BPP_32
else:
self.bpp = grf.BPP_8
self._image = None
def get_resource_files(self):
return (self.file, THIS_FILE)
def get_fingerprint(self):
return {
'class': self.__class__.__name__,
'sprite_id': self.sprite_id,
}
def get_data_layers(self):
data = self.file.get_sprite_data(self.sprite_id)
a = np.frombuffer(data, dtype=np.uint8)
assert a.size == self.w * self.h * self.grf_bpp, (a.size, self.w, self.h, self.grf_bpp)
bpp = self.grf_bpp
a.shape = (self.h, self.w, bpp)
mask = None
if self.type & 0x04 > 0:
bpp -= 1
mask = a[:, :, -1]
if bpp == 3 or bpp == 4:
rgb = a[:, :, :bpp]
return self.w, self.h, rgb, None, mask
class RecolourSprite(grf.Sprite):
def __init__(self, sprite, recolour):
super().__init__(sprite.w, sprite.h, xofs=sprite.xofs, yofs=sprite.yofs, bpp=sprite.bpp, zoom=sprite.zoom, crop=sprite.crop)
self.sprite = sprite
self.recolour = recolour
self._image = None
def get_resource_files(self):
return self.sprite.get_resource_files()
def get_fingerprint(self):
return grf.combine_fingerprint(
super().get_fingerprint_base(),
sprite=self.sprite.get_fingerprint,
recolour=True,
)
def get_image(self):
if self._image is not None:
return self._image
im, bpp = self.sprite.get_image()
assert bpp == 8
data = np.array(im)
data = self.recolour[data]
im2 = Image.fromarray(data)
im2.putpalette(im.getpalette())
self._image = im2, grf.BPP_8
return self._image
class SnowTransitionSprite(grf.Sprite):
def __init__(self, grass_sprite, snow_sprite, snow_level):
assert snow_level in (1, 2, 3)
self.grass_sprite = grass_sprite
self.snow_sprite = snow_sprite
self.snow_level = snow_level
super().__init__(
grass_sprite.w,
grass_sprite.h,
xofs=grass_sprite.xofs,
yofs=grass_sprite.yofs,
zoom=grass_sprite.zoom,
crop=grass_sprite.crop,
)
self._image = None
def get_image(self):
if self._image is not None:
return self._image
grass_data = np.array(self.grass_sprite.get_image()[0])
snow_data = np.array(self.snow_sprite.get_image()[0])
ratio = [0, 0.15, 0.35, 0.7][self.snow_level]
grass_desaturate = [0, 10, 30, 15][self.snow_level]
grass_blend = [0, 0.1, 0.2, 0.2][self.snow_level]
data = grass_data.copy()
cache = {(0, 0): 0}
h, w = data.shape
for y in range(h):
for x in range(w):
cache_key = grass_index, snow_index = snow_data[y, x], grass_data[y, x]
if cache_key in cache:
data[y, x] = cache[cache_key]
continue
snow_colour = grf.OKLAB_PALETTE[snow_index]
grass_colour = grf.OKLAB_PALETTE[grass_index]
grass_colour = grf.oklab_blend(grass_colour, grf.srgb_to_oklab((0.7, 0.7, 0)), ratio=grass_blend)
grass_colour = grass_colour.desaturate(grass_desaturate)
data[y, x] = cache[cache_key] = grf.find_best_color(snow_colour.blend(grass_colour, ratio=ratio))
im = Image.fromarray(data)
im.putpalette(grf.PALETTE)
self._image = im, grf.BPP_8
return self._image
def get_resource_files(self):
return self.grass_sprite.get_resource_files() + self.snow_sprite.get_resource_files()
def get_fingerprint(self):
return grf.combine_fingerprint(
grass=self.grass_sprite.get_fingerprint,
snow=self.snow_sprite.get_fingerprint,
level=self.snow_level,
)
def gen_recolour(color_func):
out = np.arange(256, dtype=np.uint8)
for i, c in grf.SPECTRA_PALETTE.items():
if i not in grf.SAFE_COLOURS:
continue
out[i] = grf.find_best_color(color_func(c))
return out
def gen_tint(tint, ratio):
return lambda x: grf.find_best_color(x.blend(tint, ratio=ratio))
def gen_brightness(level):
def func(x):
if level > 0:
return x.brighten(amount=2.56 * level)
else:
return x.darken(amount=-2.56 * level)
return gen_recolour(func)
def gen_land_recolour():
def func(x):
r, g, b = x.rgb
if 2 * g > r + b:
x = x.blend(spectra.rgb(0.7, 1, 0), ratio=0.05)
x = x.saturate(10)
# elif 3 * b > r + g:
# x = x.blend(spectra.rgb(0, 0, 1), ratio=0.3)
# x = x.blend(spectra.rgb(1, 0, 1), ratio=0.5)
# x = x.saturate(40)
else:
x = x.blend(spectra.rgb(0.7, 1, 0), ratio=0.05)
x = x.saturate(5)
return x
return gen_recolour(func)
def gen_land_recolour2():
def func(x):
r, g, b = x.rgb
if 3 * g / 2 > r + b:
x = x.blend(spectra.rgb(0.7, 1, 0), ratio=0.05)
x = x.saturate(10)
return x
return gen_recolour(func)
GROUND_RECOLOUR = gen_land_recolour()
g = grf.NewGRF(
grfid=b'CMAL',
name='CityMania Alpine Landscape for OpenGFX2',
description='Modified OpenGFX2 sprites for alpine climate.',
version=3,
min_compatible_version=0,
)
ogfx = GRFFile(OGFX_BASE_PATH)
def replace_old_sprites(first_id, amount, sprite_func):
g.add(grf.ReplaceOldSprites([(first_id, amount)]))
res = []
for i in range(amount):
res.append(sprite_func(first_id, i))
g.add(*res)
return res
# shore sprites (replacing 16 seems to do these as well)
# replace_shore_sprites(4062, 'gfx/water/seashore_grid_temperate.gimp.png', 1, 1)
# def replace_coastal_sprites(file, x, y, **kw):
# png = grf.ImageFile(file)
# gen.add(grf.ReplaceNewSprites(0x0d, 16))
# sprite = lambda *args, **kw: gen.add(grf.FileSprite(png, *args, **kw))
# sprite(1276+x, y, 64, 15, xofs=-31, yofs= 0, **kw)
# sprite( 80+x, y, 64, 31, xofs=-31, yofs= 0, **kw)
# sprite( 160+x, y, 64, 23, xofs=-31, yofs= 0, **kw)
# sprite( 240+x, y, 64, 23, xofs=-31, yofs= 0, **kw)
# sprite( 320+x, y, 64, 31, xofs=-31, yofs= 0, **kw)
# sprite(1356+x, y, 64, 31, xofs=-31, yofs= -8, **kw)
# sprite( 478+x, y, 64, 23, xofs=-31, yofs= 0, **kw)
# sprite( 558+x, y, 64, 23, xofs=-31, yofs= 0, **kw)
# sprite( 638+x, y, 64, 39, xofs=-31, yofs= -8, **kw)
# sprite( 718+x, y, 64, 39, xofs=-31, yofs= -8, **kw)
# sprite(1196+x, y, 64, 47, xofs=-31, yofs=-16, **kw)
# sprite( 878+x, y, 64, 31, xofs=-31, yofs= -8, **kw)
# sprite( 958+x, y, 64, 39, xofs=-31, yofs= -8, **kw)
# sprite(1038+x, y, 64, 39, xofs=-31, yofs= -8, **kw)
# sprite(1118+x, y, 64, 31, xofs=-31, yofs= -8, **kw)
# sprite(1436+x, y, 64, 31, xofs=-31, yofs= -8, **kw)
# replace_coastal_sprites('gfx/water/seashore_grid_temperate.gimp.png', 1, 1)
def ground_func(first_id, i):
ogfx_sprite = ogfx.get_sprite(first_id + i)
return RecolourSprite(ogfx_sprite, GROUND_RECOLOUR)
# Normal land
grass = replace_old_sprites(3981, 19, ground_func)
#bulldozed (bare) land and regeneration stages:
replace_old_sprites(3924, 19, ground_func)
replace_old_sprites(3943, 19, ground_func)
replace_old_sprites(3962, 19, ground_func)
# rough terrain
replace_old_sprites(4000, 19 + 4, ground_func)
# rocky terrain
replace_old_sprites(4023, 19, ground_func)
# road sprites
replace_old_sprites(1332, 19, ground_func)
replace_old_sprites(2389, 8, ground_func) # tunnels
# rail tunnels
replace_old_sprites(2365, 8, ground_func)
# hq
replace_old_sprites(2603, 29, ground_func)
# different snow densities:
snow = replace_old_sprites(4550, 19, ground_func)
replace_old_sprites(4493, 19, lambda _, i: SnowTransitionSprite(grass[i], snow[i], 1))
replace_old_sprites(4512, 19, lambda _, i: SnowTransitionSprite(grass[i], snow[i], 2))
replace_old_sprites(4531, 19, lambda _, i: SnowTransitionSprite(grass[i], snow[i], 3))
# CREEKS
def tmpl_ground_sprites(func, x, y, **kw):
func( 0+x, y, 64, 31, xofs=-31, yofs= 0, **kw) #
func( 80+x, y, 64, 31, xofs=-31, yofs= 0, **kw) # W
func( 160+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # S
func( 240+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # S W
func( 320+x, y, 64, 31, xofs=-31, yofs= 0, **kw) # E
func( 398+x, y, 64, 31, xofs=-31, yofs= 0, **kw) # E W
func( 478+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # E S
func( 558+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # E S W
func( 638+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N
func( 718+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N W
func( 798+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N S
func( 878+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N S W
func( 958+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N E
func(1038+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N E W
func(1118+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N E S
func(1196+x, y, 64, 47, xofs=-31, yofs=-16, **kw) # N E W STEEP
func(1276+x, y, 64, 15, xofs=-31, yofs= 0, **kw) # E S W STEEP
func(1356+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N S W STEEP
func(1436+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N E S STEEP
# Tile slope to sprite offset
get_tile_slope_offset = grf.Switch(
feature=grf.OBJECT,
ref_id=0,
ranges={0: 0, 1: 1, 2: 2, 4: 4, 8: 8, 9: 9, 3: 3, 6: 6, 12: 12, 5: 5, 10: 10, 11: 11, 7: 7, 14: 14, 13: 13, 27: 17, 23: 16, 30: 18, 29: 15},
default=0,
code='tile_slope'
)
g.add(get_tile_slope_offset)
# Ground sprite
get_ground_sprite = grf.Switch(
feature=grf.OBJECT,
ref_id=1,
ranges={-2: 4550 , -1: 4550 - 19, 0: 4550 - 19 * 2, 1: 4550 - 19 * 3},
default=3981,
code='max(snowline_height - tile_height, -2)'
)
g.add(get_ground_sprite)
g.add(grf.Action1(grf.OBJECT, 81, 19))
png = grf.ImageFile("gfx/rivers.png")
for i in range(81):
tmpl_ground_sprites(lambda *args, **kw: g.add(grf.FileSprite(png, *args, **kw)), 1, i * 64 + 1)
for i in range(81):
g.add(layout := grf.AdvancedSpriteLayout(
feature=grf.OBJECT,
ground={
'sprite': grf.SpriteRef(0, is_global=True),
'flags': 2,
'add': grf.Temp(1),
},
buildings=[{
'sprite': grf.SpriteRef(i, is_global=False),
'flags': 2,
'add': grf.Temp(0),
}]
))
g.add(layout_switch := grf.Switch(
ranges={0: layout},
default=layout,
code=f'''
TEMP[0] = call({get_tile_slope_offset})
TEMP[1] = call({get_ground_sprite}) + TEMP[0]
'''
))
g.add(creek_obj := grf.Define(
feature=grf.OBJECT,
id=i,
props={
'class' : b'CREE',
'size' : (1, 1),
'climates_available' : grf.ALL_CLIMATES,
'end_of_life_date' : 0,
'flags' : grf.Object.Flags.HAS_NO_FOUNDATION | grf.Object.Flags.ALLOW_UNDER_BRIDGE | grf.Object.Flags.AUTOREMOVE,
}
))
g.add(grf.Map(
definition=creek_obj,
maps={255: layout_switch},
default=layout_switch,
))
grf.main(g, 'alpine.grf')