Alpine climate grf (unfinished)
1707
grf/alpine/alpine.nml
Normal file
179
grf/alpine/alpine.py
Normal file
@@ -0,0 +1,179 @@
|
||||
import grf
|
||||
import spectra
|
||||
|
||||
gen = grf.NewGRF(
|
||||
b'CMAL',
|
||||
'CityMania Alpine Landscape',
|
||||
'Modified OpenGFX spritess for alpine climate.',
|
||||
)
|
||||
|
||||
|
||||
def replace_ground_sprites(sprite_id, file, x, y, **kw):
|
||||
png = grf.ImageFile(file)
|
||||
gen.add_sprite(grf.ReplaceSprites([(sprite_id, 19)]))
|
||||
sprite = lambda *args, **kw: gen.add_sprite(grf.FileSprite(png, *args, **kw))
|
||||
sprite( 0+x, y, 64, 31, xofs=-31, yofs= 0, **kw) #
|
||||
sprite( 80+x, y, 64, 31, xofs=-31, yofs= 0, **kw) # W
|
||||
sprite( 160+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # S
|
||||
sprite( 240+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # S W
|
||||
sprite( 320+x, y, 64, 31, xofs=-31, yofs= 0, **kw) # E
|
||||
sprite( 398+x, y, 64, 31, xofs=-31, yofs= 0, **kw) # E W
|
||||
sprite( 478+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # E S
|
||||
sprite( 558+x, y, 64, 23, xofs=-31, yofs= 0, **kw) # E S W
|
||||
sprite( 638+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N
|
||||
sprite( 718+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N W
|
||||
sprite( 798+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N S
|
||||
sprite( 878+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N S W
|
||||
sprite( 958+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N E
|
||||
sprite(1038+x, y, 64, 39, xofs=-31, yofs= -8, **kw) # N E W
|
||||
sprite(1118+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N E S
|
||||
sprite(1196+x, y, 64, 47, xofs=-31, yofs=-16, **kw) # N E W STEEP
|
||||
sprite(1276+x, y, 64, 15, xofs=-31, yofs= 0, **kw) # E S W STEEP
|
||||
sprite(1356+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N S W STEEP
|
||||
sprite(1436+x, y, 64, 31, xofs=-31, yofs= -8, **kw) # N E S STEEP
|
||||
|
||||
|
||||
def replace_shore_sprites(sprite_id, file, x, y, **kw):
|
||||
png = grf.ImageFile(file)
|
||||
gen.add_sprite(grf.ReplaceSprites([(sprite_id, 8)]))
|
||||
sprite = lambda *args, **kw: gen.add_sprite(grf.FileSprite(png, *args, **kw))
|
||||
sprite(320+x, y, 64, 31, 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(638+x, y, 64, 39, xofs=-31, yofs=-8, **kw)
|
||||
sprite(478+x, y, 64, 23, xofs=-31, yofs= 0, **kw)
|
||||
sprite(958+x, y, 64, 39, xofs=-31, yofs=-8, **kw)
|
||||
sprite(240+x, y, 64, 23, xofs=-31, yofs= 0, **kw)
|
||||
sprite(718+x, y, 64, 39, xofs=-31, yofs=-8, **kw)
|
||||
|
||||
|
||||
def replace_additional_rough_sprites(sprite_id, file, x, y, **kw):
|
||||
png = grf.ImageFile(file)
|
||||
gen.add_sprite(grf.ReplaceSprites([(sprite_id, 4)]))
|
||||
sprite = lambda *args, **kw: gen.add_sprite(grf.FileSprite(png, *args, **kw))
|
||||
sprite( x, y, 64, 31, xofs=-31, yofs=0, **kw)
|
||||
sprite( 80+x, y, 64, 31, xofs=-31, yofs=0, **kw)
|
||||
sprite(160+x, y, 64, 31, xofs=-31, yofs=0, **kw)
|
||||
sprite(240+x, y, 64, 31, xofs=-31, yofs=0, **kw)
|
||||
|
||||
# Normal land
|
||||
replace_ground_sprites(3981, 'gfx/grass_grid_temperate.gimp.png', 1, 1)
|
||||
|
||||
# bulldozed (bare) land and regeneration stages:
|
||||
replace_ground_sprites(3924, 'gfx/bare03_grid.gimp.png', 1, 1)
|
||||
replace_ground_sprites(3943, 'gfx/bare13_grid_temperate.gimp.png', 1, 1)
|
||||
replace_ground_sprites(3962, 'gfx/bare23_grid_temperate.gimp.png', 1, 1)
|
||||
|
||||
# rough terrain
|
||||
replace_ground_sprites(4000, 'gfx/rough_grid_temperate.gimp.png', 1, 1)
|
||||
replace_additional_rough_sprites(4019, 'gfx/rough_grid_temperate.gimp.png', 1511, 1)
|
||||
|
||||
# rocky terrain
|
||||
replace_ground_sprites(4023, 'gfx/rocks_grid_temperate.gimp.png', 1, 1)
|
||||
|
||||
# different snow densities:
|
||||
replace_ground_sprites(4493, 'gfx/snow14_grid_alpine.gimp.png', 1, 1)
|
||||
replace_ground_sprites(4512, 'gfx/snow24_grid_alpine.gimp.png', 1, 1)
|
||||
replace_ground_sprites(4531, 'gfx/snow34_grid_alpine.gimp.png', 1, 1)
|
||||
replace_ground_sprites(4550, 'gfx/snow_grid.gimp.png', 1, 1)
|
||||
|
||||
# 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_sprite(grf.ReplaceNewSprites(0x0d, 16))
|
||||
sprite = lambda *args, **kw: gen.add_sprite(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)
|
||||
|
||||
|
||||
# CREEKS
|
||||
|
||||
|
||||
|
||||
|
||||
# MEADOW
|
||||
|
||||
# spriteset (meadow_groundsprites, "gfx/meadow_grid_temperate.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
# spriteset (meadow_transitions, "gfx/meadow_transitions.png") {
|
||||
# tmpl_groundsprites(1, 0 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 1 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 2 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 3 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 4 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 5 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 6 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 7 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 8 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 9 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 10 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 11 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 12 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 13 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 14 * 64 + 1)
|
||||
# tmpl_groundsprites(1, 15 * 64 + 1)
|
||||
# }
|
||||
|
||||
# spritelayout meadow_groundsprites_default {
|
||||
# ground {
|
||||
# sprite: meadow_transitions(
|
||||
# slope_to_sprite_offset(tile_slope)
|
||||
# + (nearby_tile_object_type( 0, -1) == meadow && nearby_tile_object_type(-1, -1) == meadow &&nearby_tile_object_type(-1, 0) == meadow ? 0 : 19)
|
||||
# + (nearby_tile_object_type(-1, 0) == meadow && nearby_tile_object_type(-1, 1) == meadow &&nearby_tile_object_type( 0, 1) == meadow ? 0 : 38)
|
||||
# + (nearby_tile_object_type( 0, 1) == meadow && nearby_tile_object_type( 1, 1) == meadow &&nearby_tile_object_type( 1, 0) == meadow ? 0 : 76)
|
||||
# + (nearby_tile_object_type( 1, 0) == meadow && nearby_tile_object_type( 1, -1) == meadow &&nearby_tile_object_type( 0, -1) == meadow ? 0 : 152)
|
||||
# );
|
||||
# }
|
||||
# }
|
||||
|
||||
# spritelayout meadow_groundsprites_purchase {
|
||||
# ground {
|
||||
# sprite: meadow_groundsprites;
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
# switch (FEAT_OBJECTS, SELF, switch_meadow_groundsprites_default, [
|
||||
# STORE_TEMP(slope_to_sprite_offset(tile_slope), 0)
|
||||
# ]) {
|
||||
# meadow_groundsprites_default;
|
||||
# }
|
||||
|
||||
|
||||
# item (FEAT_OBJECTS, meadow) {
|
||||
# property {
|
||||
# class: "FLMA";
|
||||
# classname: string(STR_FLMA);
|
||||
# name: string(STR_TEST_OBJECT);
|
||||
# climates_available: ALL_CLIMATES;
|
||||
# end_of_life_date: 0xFFFFFFFF;
|
||||
# object_flags:bitmask(OBJ_FLAG_ALLOW_BRIDGE, OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_NO_FOUNDATIONS);
|
||||
# size: [1,1];
|
||||
# }
|
||||
# graphics {
|
||||
# default: meadow_groundsprites_default;
|
||||
# purchase: meadow_groundsprites_purchase;
|
||||
# tile_check: CB_RESULT_LOCATION_ALLOW;
|
||||
# }
|
||||
# }
|
||||
|
||||
gen.write('alpine.grf')
|
||||
408
grf/alpine/alpine_gen.py
Normal file
@@ -0,0 +1,408 @@
|
||||
print("""\
|
||||
grf {
|
||||
grfid: "CMAL";
|
||||
name: string(STR_GRF_NAME);
|
||||
desc: string(STR_GRF_DESCRIPTION);
|
||||
version: 1;
|
||||
min_compatible_version: 1;
|
||||
}
|
||||
|
||||
template tmpl_groundsprites_flags(x, y, flags) {
|
||||
// N E S W STEEP
|
||||
[ 0+x, y, 64, 31, -31, 0, flags ] //
|
||||
[ 80+x, y, 64, 31, -31, 0, flags ] // W
|
||||
[ 160+x, y, 64, 23, -31, 0, flags ] // S
|
||||
[ 240+x, y, 64, 23, -31, 0, flags ] // S W
|
||||
|
||||
[ 320+x, y, 64, 31, -31, 0, flags ] // E
|
||||
[ 398+x, y, 64, 31, -31, 0, flags ] // E W
|
||||
[ 478+x, y, 64, 23, -31, 0, flags ] // E S
|
||||
[ 558+x, y, 64, 23, -31, 0, flags ] // E S W
|
||||
|
||||
[ 638+x, y, 64, 39, -31, -8, flags ] // N
|
||||
[ 718+x, y, 64, 39, -31, -8, flags ] // N W
|
||||
[ 798+x, y, 64, 31, -31, -8, flags ] // N S
|
||||
[ 878+x, y, 64, 31, -31, -8, flags ] // N S W
|
||||
|
||||
[ 958+x, y, 64, 39, -31, -8, flags ] // N E
|
||||
[1038+x, y, 64, 39, -31, -8, flags ] // N E W
|
||||
[1118+x, y, 64, 31, -31, -8, flags ] // N E S
|
||||
[1196+x, y, 64, 47, -31,-16, flags ] // N E W STEEP
|
||||
|
||||
[1276+x, y, 64, 15, -31, 0, flags ] // E S W STEEP
|
||||
[1356+x, y, 64, 31, -31, -8, flags ] // N S W STEEP
|
||||
[1436+x, y, 64, 31, -31, -8, flags ] // N E S STEEP
|
||||
}
|
||||
|
||||
template tmpl_groundsprites(x, y) {
|
||||
tmpl_groundsprites_flags(x, y, 0)
|
||||
}
|
||||
|
||||
template tmpl_groundsprites_anim(x, y) {
|
||||
tmpl_groundsprites_flags(x, y, ANIM)
|
||||
}
|
||||
|
||||
template tmpl_level_ground(x, y) {
|
||||
[ x, y, 64, 31, -31, 0 ]
|
||||
}
|
||||
|
||||
template tmpl_rough(x, y) {
|
||||
tmpl_level_ground( x, y)
|
||||
tmpl_level_ground( 80+x, y)
|
||||
tmpl_level_ground(160+x, y)
|
||||
tmpl_level_ground(240+x, y)
|
||||
}
|
||||
|
||||
template tmpl_additional_rough(x, y) {
|
||||
tmpl_rough(1510+x, y)
|
||||
}
|
||||
|
||||
template tmpl_16shore_tiles(x, y) {
|
||||
[1276+x, y, 64, 15, -31, 0 ]
|
||||
[ 80+x, y, 64, 31, -31, 0 ]
|
||||
[ 160+x, y, 64, 23, -31, 0 ]
|
||||
[ 240+x, y, 64, 23, -31, 0 ]
|
||||
|
||||
[ 320+x, y, 64, 31, -31, 0 ]
|
||||
[1356+x, y, 64, 31, -31, -8 ]
|
||||
[ 478+x, y, 64, 23, -31, 0 ]
|
||||
[ 558+x, y, 64, 23, -31, 0 ]
|
||||
|
||||
[ 638+x, y, 64, 39, -31, -8 ]
|
||||
[ 718+x, y, 64, 39, -31, -8 ]
|
||||
[1196+x, y, 64, 47, -31,-16 ]
|
||||
[ 878+x, y, 64, 31, -31, -8 ]
|
||||
|
||||
[ 958+x, y, 64, 39, -31, -8 ]
|
||||
[1038+x, y, 64, 39, -31, -8 ]
|
||||
[1118+x, y, 64, 31, -31, -8 ]
|
||||
[1436+x, y, 64, 31, -31, -8 ]
|
||||
}
|
||||
|
||||
template tmpl_8shore_tiles(x, y) {
|
||||
[ 320+x, y, 64, 31, -31, 0 ]
|
||||
[ 80+x, y, 64, 31, -31, 0 ]
|
||||
[ 160+x, y, 64, 23, -31, 0 ]
|
||||
[ 638+x, y, 64, 39, -31, -8 ]
|
||||
|
||||
[ 478+x, y, 64, 23, -31, 0 ]
|
||||
[ 958+x, y, 64, 39, -31, -8 ]
|
||||
[ 240+x, y, 64, 23, -31, 0 ]
|
||||
[ 718+x, y, 64, 39, -31, -8 ]
|
||||
}
|
||||
|
||||
template tmpl_tree_wide() {
|
||||
[ 0, 0, 45, 80, -24, -73]
|
||||
[ 50, 0, 45, 80, -24, -73]
|
||||
[100, 0, 45, 80, -24, -73]
|
||||
[150, 0, 45, 80, -24, -73]
|
||||
[200, 0, 45, 80, -24, -73]
|
||||
[250, 0, 45, 80, -24, -73]
|
||||
[300, 0, 45, 80, -24, -73]
|
||||
}
|
||||
|
||||
template tmpl_tree_narrow() {
|
||||
[ 0, 0, 35, 80, -19, -73]
|
||||
[ 40, 0, 35, 80, -19, -73]
|
||||
[ 80, 0, 35, 80, -19, -73]
|
||||
[120, 0, 35, 80, -19, -73]
|
||||
[160, 0, 35, 80, -19, -73]
|
||||
[200, 0, 35, 80, -19, -73]
|
||||
[240, 0, 35, 80, -19, -73]
|
||||
}
|
||||
|
||||
// Normal land:
|
||||
replace (3981, "gfx/grass_grid_temperate.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
// bulldozed (bare) land and regeneration stages:
|
||||
replace (3924, "gfx/bare03_grid.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
replace (3943, "gfx/bare13_grid_temperate.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
replace (3962, "gfx/bare23_grid_temperate.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
// rough terrain
|
||||
replace (4000, "gfx/rough_grid_temperate.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
replace (4019, "gfx/rough_grid_temperate.gimp.png") { tmpl_additional_rough(1, 1) }
|
||||
|
||||
// rocky terrain
|
||||
replace (4023, "gfx/rocks_grid_temperate.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
// different snow densities:
|
||||
replace (4493, "gfx/snow14_grid_alpine.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
replace (4512, "gfx/snow24_grid_alpine.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
replace (4531, "gfx/snow34_grid_alpine.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
replace (4550, "gfx/snow_grid.gimp.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
// shore sprites
|
||||
replace (4062, "gfx/water/seashore_grid_temperate.gimp.png") { tmpl_8shore_tiles(1, 1) }
|
||||
replacenew (COAST_TILES, "gfx/water/seashore_grid_temperate.gimp.png") { tmpl_16shore_tiles(1, 1) }
|
||||
|
||||
|
||||
// //Arctic trees: the following trees have snowy equivalents:
|
||||
// base_graphics spr1709(1709, "trees/tree_01_conifer.gimp.png") { tmpl_tree_narrow() } // 1709 conifer (snowy: 1765)
|
||||
// base_graphics spr1716(1716, "trees/tree_06_leaf.gimp.png") { tmpl_tree_narrow() } // 1716 leaf tree (snowy: 1772)
|
||||
// base_graphics spr1723(1723, "trees/tree_07_leaf.gimp.png") { tmpl_tree_narrow() } // 1723 leaf tree (snowy: 1779)
|
||||
// base_graphics spr1730(1730, "trees/tree_08_conifer.gimp.png") { tmpl_tree_narrow() } // 1730 conifer (snowy: 1786)
|
||||
// base_graphics spr1737(1737, "trees/tree_09_conifer.gimp.png") { tmpl_tree_narrow() } // 1737 conifer (snowy: 1793)
|
||||
// base_graphics spr1744(1744, "trees/tree_04_conifer.gimp.png") { tmpl_tree_narrow() } // 1744 conifer (snowy: 1800)
|
||||
// base_graphics spr1751(1751, "trees/tree_05_conifer.gimp.png") { tmpl_tree_narrow() } // 1751 conifer (snowy: 1807)
|
||||
// base_graphics spr1758(1758, "trees/tree_10_leaf.gimp.png") { tmpl_tree_narrow() } // 1758 leaf tree (snowy: 1814)
|
||||
// // snowy trees
|
||||
// base_graphics spr1765(1765, "trees/tree_01_snow_conifer.gimp.png") { tmpl_tree_narrow() } // 1765 snowy conifer (equiv of 1709)
|
||||
// base_graphics spr1772(1772, "trees/tree_06_snow_leaf.gimp.png") { tmpl_tree_narrow() } // 1772 snowy leaf tree (equiv. of 1716)
|
||||
// base_graphics spr1779(1779, "trees/tree_07_snow_leaf.gimp.png") { tmpl_tree_narrow() } // 1779 snowy leaf tree (equiv. of 1723)
|
||||
// base_graphics spr1786(1786, "trees/tree_08_snow_conifer.gimp.png") { tmpl_tree_narrow() } // 1786 snowy conifer (equiv. of 1730)
|
||||
// base_graphics spr1793(1793, "trees/tree_09_snow_conifer.gimp.png") { tmpl_tree_narrow() } // 1793 snowy conifer (equiv. of 1737)
|
||||
// base_graphics spr1800(1800, "trees/tree_04_snow_conifer.gimp.png") { tmpl_tree_narrow() } // 1800 snowy conifer (equiv. of 1744)
|
||||
// base_graphics spr1807(1807, "trees/tree_05_snow_conifer.gimp.png") { tmpl_tree_narrow() } // 1807 snowy conifer (equiv. of 1751)
|
||||
// base_graphics spr1814(1814, "trees/tree_10_snow_leaf.gimp.png") { tmpl_tree_narrow() } // 1814 snowy leaf tree (equiv. of 1758)
|
||||
|
||||
spriteset (meadow_groundsprites, "gfx/meadow_grid_temperate.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
|
||||
spriteset (meadow_transitions, "gfx/meadow_transitions.png") {
|
||||
tmpl_groundsprites(1, 0 * 64 + 1)
|
||||
tmpl_groundsprites(1, 1 * 64 + 1)
|
||||
tmpl_groundsprites(1, 2 * 64 + 1)
|
||||
tmpl_groundsprites(1, 3 * 64 + 1)
|
||||
tmpl_groundsprites(1, 4 * 64 + 1)
|
||||
tmpl_groundsprites(1, 5 * 64 + 1)
|
||||
tmpl_groundsprites(1, 6 * 64 + 1)
|
||||
tmpl_groundsprites(1, 7 * 64 + 1)
|
||||
tmpl_groundsprites(1, 8 * 64 + 1)
|
||||
tmpl_groundsprites(1, 9 * 64 + 1)
|
||||
tmpl_groundsprites(1, 10 * 64 + 1)
|
||||
tmpl_groundsprites(1, 11 * 64 + 1)
|
||||
tmpl_groundsprites(1, 12 * 64 + 1)
|
||||
tmpl_groundsprites(1, 13 * 64 + 1)
|
||||
tmpl_groundsprites(1, 14 * 64 + 1)
|
||||
tmpl_groundsprites(1, 15 * 64 + 1)
|
||||
}
|
||||
|
||||
spritelayout meadow_groundsprites_default {
|
||||
ground {
|
||||
sprite: meadow_transitions(
|
||||
slope_to_sprite_offset(tile_slope)
|
||||
+ (nearby_tile_object_type( 0, -1) == meadow && nearby_tile_object_type(-1, -1) == meadow &&nearby_tile_object_type(-1, 0) == meadow ? 0 : 19)
|
||||
+ (nearby_tile_object_type(-1, 0) == meadow && nearby_tile_object_type(-1, 1) == meadow &&nearby_tile_object_type( 0, 1) == meadow ? 0 : 38)
|
||||
+ (nearby_tile_object_type( 0, 1) == meadow && nearby_tile_object_type( 1, 1) == meadow &&nearby_tile_object_type( 1, 0) == meadow ? 0 : 76)
|
||||
+ (nearby_tile_object_type( 1, 0) == meadow && nearby_tile_object_type( 1, -1) == meadow &&nearby_tile_object_type( 0, -1) == meadow ? 0 : 152)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
spritelayout meadow_groundsprites_purchase {
|
||||
ground {
|
||||
sprite: meadow_groundsprites;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (FEAT_OBJECTS, SELF, switch_meadow_groundsprites_default, [
|
||||
STORE_TEMP(slope_to_sprite_offset(tile_slope), 0)
|
||||
]) {
|
||||
meadow_groundsprites_default;
|
||||
}
|
||||
|
||||
|
||||
item (FEAT_OBJECTS, meadow) {
|
||||
property {
|
||||
class: "FLMA";
|
||||
classname: string(STR_FLMA);
|
||||
name: string(STR_TEST_OBJECT);
|
||||
climates_available: ALL_CLIMATES;
|
||||
end_of_life_date: 0xFFFFFFFF;
|
||||
object_flags:bitmask(OBJ_FLAG_ALLOW_BRIDGE, OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_NO_FOUNDATIONS);
|
||||
size: [1,1];
|
||||
}
|
||||
graphics {
|
||||
default: meadow_groundsprites_default;
|
||||
purchase: meadow_groundsprites_purchase;
|
||||
tile_check: CB_RESULT_LOCATION_ALLOW;
|
||||
}
|
||||
}
|
||||
|
||||
spriteset (creek_groundsprites, "gfx/rivers.png") {
|
||||
tmpl_groundsprites_anim(1, 0 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 1 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 2 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 3 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 4 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 5 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 6 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 7 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 8 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 9 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 10 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 11 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 12 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 13 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 14 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 15 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 16 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 17 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 18 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 19 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 20 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 21 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 22 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 23 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 24 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 25 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 26 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 27 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 28 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 29 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 30 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 31 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 32 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 33 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 34 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 35 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 36 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 37 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 38 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 39 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 40 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 41 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 42 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 43 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 44 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 45 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 46 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 47 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 48 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 49 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 50 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 51 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 52 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 53 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 54 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 55 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 56 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 57 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 58 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 59 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 60 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 61 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 62 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 63 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 64 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 65 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 66 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 67 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 68 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 69 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 70 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 71 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 72 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 73 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 74 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 75 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 76 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 77 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 78 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 79 * 64 + 1)
|
||||
tmpl_groundsprites_anim(1, 80 * 64 + 1)
|
||||
}
|
||||
|
||||
spritelayout creek_groundsprites_default(n, tile_height) {
|
||||
ground {
|
||||
sprite:
|
||||
(climate == CLIMATE_ARCTIC && tile_height > snowline_height - 2 ?
|
||||
GROUNDSPRITE_SNOW + min(tile_height - snowline_height - 2, 0) * 19
|
||||
: (terrain_type == TILETYPE_DESERT ? GROUNDSPRITE_DESERT : GROUNDSPRITE_NORMAL))
|
||||
+ slope_to_sprite_offset(tile_slope);
|
||||
}
|
||||
childsprite {
|
||||
sprite: creek_groundsprites(
|
||||
slope_to_sprite_offset(tile_slope) + 19 * n
|
||||
);
|
||||
always_draw: 1;
|
||||
}
|
||||
}
|
||||
|
||||
spritelayout creek_groundsprites_purchase(n) {
|
||||
ground { sprite: GROUNDSPRITE_NORMAL; }
|
||||
childsprite {
|
||||
sprite: creek_groundsprites(19 * n);
|
||||
always_draw: 1;
|
||||
}
|
||||
}
|
||||
|
||||
""")
|
||||
|
||||
print('spriteset (mine_number_sprites, "gfx/mine_numbers.png") {')
|
||||
for i in range(10):
|
||||
print(f' [{i * 12}, 0, 12, 16, -6, 8]')
|
||||
print('}')
|
||||
|
||||
print(f""" \
|
||||
spritelayout minefield_sprites(n) {{
|
||||
ground {{ sprite: GROUNDSPRITE_NORMAL + slope_to_sprite_offset(tile_slope); }}
|
||||
childsprite {{
|
||||
sprite: mine_number_sprites(n);
|
||||
always_draw: 1;
|
||||
}}
|
||||
}}
|
||||
""")
|
||||
|
||||
print(f""" \
|
||||
item (FEAT_OBJECTS, minefield) {{
|
||||
property {{
|
||||
class: "MINE";
|
||||
classname: string(STR_MINEFIELD);
|
||||
name: string(STR_TEST_OBJECT);
|
||||
climates_available: ALL_CLIMATES;
|
||||
end_of_life_date: 0xFFFFFFFF;
|
||||
object_flags:bitmask(OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_NO_FOUNDATIONS);
|
||||
size: [1,1];
|
||||
num_views: 2;
|
||||
}}
|
||||
graphics {{
|
||||
default: minefield_sprites(
|
||||
(
|
||||
(nearby_tile_object_type(-1, -1) == minefield) +
|
||||
(nearby_tile_object_type(-1, 0) == minefield) +
|
||||
(nearby_tile_object_type(-1, 1) == minefield) +
|
||||
(nearby_tile_object_type(0, -1) == minefield) +
|
||||
(nearby_tile_object_type(0, 1) == minefield) +
|
||||
(nearby_tile_object_type(1, -1) == minefield) +
|
||||
(nearby_tile_object_type(1, 0) == minefield) +
|
||||
(nearby_tile_object_type(1, 1) == minefield)
|
||||
) == 8 ? 9 : (
|
||||
(nearby_tile_object_view(-1, -1)) +
|
||||
(nearby_tile_object_view(-1, 0)) +
|
||||
(nearby_tile_object_view(-1, 1)) +
|
||||
(nearby_tile_object_view(0, -1)) +
|
||||
(nearby_tile_object_view(0, 1)) +
|
||||
(nearby_tile_object_view(0, 0)) +
|
||||
(nearby_tile_object_view(1, -1)) +
|
||||
(nearby_tile_object_view(1, 0)) +
|
||||
(nearby_tile_object_view(1, 1))
|
||||
)
|
||||
);
|
||||
purchase: minefield_sprites(9);
|
||||
tile_check: CB_RESULT_LOCATION_ALLOW;
|
||||
}}
|
||||
}}
|
||||
""")
|
||||
|
||||
for i in range(81):
|
||||
print(f"""\
|
||||
item (FEAT_OBJECTS, rivers_{i}) {{
|
||||
property {{
|
||||
class: "CREE";
|
||||
classname: string(STR_CREEK);
|
||||
name: string(STR_TEST_OBJECT);
|
||||
climates_available: ALL_CLIMATES;
|
||||
end_of_life_date: 0xFFFFFFFF;
|
||||
object_flags:bitmask(OBJ_FLAG_ALLOW_BRIDGE, OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_NO_FOUNDATIONS);
|
||||
size: [1,1];
|
||||
}}
|
||||
graphics {{
|
||||
default: creek_groundsprites_default({i}, nearby_tile_height(0, 0));
|
||||
purchase: creek_groundsprites_purchase({i});
|
||||
tile_check: CB_RESULT_LOCATION_ALLOW;
|
||||
}}
|
||||
}}
|
||||
""")
|
||||
302
grf/alpine/gen_sprites.py
Normal file
@@ -0,0 +1,302 @@
|
||||
from PIL import Image, ImageDraw
|
||||
import numpy as np
|
||||
|
||||
import math
|
||||
import os
|
||||
import spectra
|
||||
|
||||
SAFE_COLORS = list(range(1, 0xD7))
|
||||
f = open("../ttd-newgrf-dos.gpl")
|
||||
|
||||
while f.readline().strip() != "#":
|
||||
pass
|
||||
|
||||
|
||||
colors = []
|
||||
for _ in range(256):
|
||||
try:
|
||||
r, g, b, _, i = f.readline().split()
|
||||
except ValueError:
|
||||
break
|
||||
c = spectra.rgb(float(r) / 255., float(g) / 255., float(b) / 255.)
|
||||
# if c in SAFE_COLORS:
|
||||
colors.append((int(i), c))
|
||||
|
||||
def color_distance(c1, c2):
|
||||
rmean = (c1.rgb[0] + c2.rgb[0]) / 2.
|
||||
r = c1.rgb[0] - c2.rgb[0]
|
||||
g = c1.rgb[1] - c2.rgb[1]
|
||||
b = c1.rgb[2] - c2.rgb[2]
|
||||
return math.sqrt(
|
||||
((2 + rmean) * r * r) +
|
||||
4 * g * g +
|
||||
(3 - rmean) * b * b)
|
||||
|
||||
def find_best_color(x):
|
||||
mj, md = 0, 1e100
|
||||
for j, c in colors:
|
||||
if j not in SAFE_COLORS:
|
||||
continue
|
||||
d = color_distance(x, c)
|
||||
if d < md:
|
||||
mj, md = j, d
|
||||
return mj
|
||||
|
||||
def gen_recolor(color_func):
|
||||
out = np.arange(256, dtype=np.uint8)
|
||||
for i, c in colors:
|
||||
if i not in SAFE_COLORS:
|
||||
continue
|
||||
out[i] = find_best_color(color_func(c))
|
||||
return out
|
||||
|
||||
def gen_tint(tint, ratio):
|
||||
return lambda x: 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_recolor(func)
|
||||
|
||||
# def gen_land_recolor():
|
||||
# def func(x):
|
||||
# if 2 * g > r + b:
|
||||
# x = x.blend(spectra.rgb(0.7, 1, 0), ratio=0.2)
|
||||
# else:
|
||||
# x = x.saturate(20)
|
||||
# return x
|
||||
# return gen_recolor(func)
|
||||
|
||||
def gen_land_recolor():
|
||||
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(20)
|
||||
# # 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_recolor(func)
|
||||
|
||||
def remap_file(f_in, f_out, palmap):
|
||||
print(f"Converting {f_out}...")
|
||||
im = Image.open(f_in)
|
||||
data = np.array(im)
|
||||
data = palmap[data]
|
||||
im2 = Image.fromarray(data)
|
||||
im2.putpalette(im.getpalette())
|
||||
im2.save(f_out)
|
||||
|
||||
|
||||
SOURCE_DIR = "/home/pavels/Projects/cmclient/local/ogfx-landscape-1.1.2-source/src/gfx"
|
||||
DEST_DIR = "gfx"
|
||||
land_palmap = gen_land_recolor()
|
||||
|
||||
for fname in ("grass_grid_temperate.gimp.png",
|
||||
"bare03_grid.gimp.png",
|
||||
"bare13_grid_temperate.gimp.png",
|
||||
"bare23_grid_temperate.gimp.png",
|
||||
"rough_grid_temperate.gimp.png",
|
||||
"rough_grid_temperate.gimp.png",
|
||||
"rocks_grid_temperate.gimp.png",
|
||||
"snow14_grid_alpine.gimp.png",
|
||||
"snow24_grid_alpine.gimp.png",
|
||||
"snow34_grid_alpine.gimp.png",
|
||||
"snow_grid.gimp.png",
|
||||
"water/seashore_grid_temperate.gimp.png",
|
||||
):
|
||||
remap_file(os.path.join(SOURCE_DIR, fname), os.path.join(DEST_DIR, fname), land_palmap)
|
||||
|
||||
# SOURCE_DIR = "/home/pavels/Builds/OpenGFX"
|
||||
|
||||
# TREES = [
|
||||
# "sprites/png/trees/arctic/tree_01_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_06_leaf.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_07_leaf.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_08_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_09_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_04_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_05_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_10_leaf.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_01_snow_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_06_snow_leaf.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_07_snow_leaf.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_08_snow_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_09_snow_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_04_snow_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_05_snow_conifer.gimp.png",
|
||||
# "sprites/png/trees/arctic/tree_10_snow_leaf.gimp.png",
|
||||
# ]
|
||||
|
||||
# for fname in TREES:
|
||||
|
||||
def meadow_recolor(x):
|
||||
x = x.blend(spectra.rgb(0.7, 1, 0), ratio=0.2)
|
||||
return x.blend(spectra.rgb(1, 1, 0), ratio=0.4)
|
||||
|
||||
def half_meadow_recolor(x):
|
||||
x = x.blend(spectra.rgb(0.7, 1, 0), ratio=0.2)
|
||||
return x.blend(spectra.rgb(1, 1, 0), ratio=0.2)
|
||||
|
||||
remap_file(os.path.join(SOURCE_DIR, "grass_grid_temperate.gimp.png"), os.path.join(DEST_DIR, "meadow_grid_temperate.png"), gen_recolor(meadow_recolor))
|
||||
remap_file(os.path.join(SOURCE_DIR, "grass_grid_temperate.gimp.png"), os.path.join(DEST_DIR, "half_meadow_grid_temperate.png"), gen_recolor(half_meadow_recolor))
|
||||
|
||||
# Generate meadow transition tiles
|
||||
im = Image.open(os.path.join(SOURCE_DIR, "grass_grid_temperate.gimp.png"))
|
||||
din = np.array(im)
|
||||
|
||||
GROUND_SPRITES = [
|
||||
# N E S W STEEP
|
||||
[ 0 + 1, 1, 64, 31, -31, 0, 15, 15], #
|
||||
[ 80 + 1, 1, 64, 31, -31, 0, 7, 15], # W
|
||||
[ 160 + 1, 1, 64, 23, -31, 0, 15, 15], # S
|
||||
[ 240 + 1, 1, 64, 23, -31, 0, 7, 15], # S W
|
||||
[ 320 + 1, 1, 64, 31, -31, 0, 15, 7], # E
|
||||
[ 398 + 1, 1, 64, 31, -31, 0, 7, 7], # E W
|
||||
[ 478 + 1, 1, 64, 23, -31, 0, 15, 7], # E S
|
||||
[ 558 + 1, 1, 64, 23, -31, 0, 7, 7], # E S W
|
||||
[ 638 + 1, 1, 64, 39, -31, -8, 23, 23], # N
|
||||
[ 718 + 1, 1, 64, 39, -31, -8, 15, 23], # N W
|
||||
[ 798 + 1, 1, 64, 31, -31, -8, 23, 23], # N S
|
||||
[ 878 + 1, 1, 64, 31, -31, -8, 15, 23], # N S W
|
||||
[ 958 + 1, 1, 64, 39, -31, -8, 23, 15], # N E
|
||||
[1038 + 1, 1, 64, 39, -31, -8, 15, 15], # N E W
|
||||
[1118 + 1, 1, 64, 31, -31, -8, 23, 15], # N E S
|
||||
[1196 + 1, 1, 64, 47, -31,-16, 23, 23], # N E W STEEP
|
||||
[1276 + 1, 1, 64, 15, -31, 0, 7, 7], # E S W STEEP
|
||||
[1356 + 1, 1, 64, 31, -31, -8, 7, 23], # N S W STEEP
|
||||
[1436 + 1, 1, 64, 31, -31, -8, 23, 7], # N E S STEEP
|
||||
]
|
||||
|
||||
# dout = np.zeros((64 * 8, 31 + 47 + 15), dtype=np.uint8)
|
||||
# dout = np.zeros((31 + 47 + 15, 64 * 8), dtype=np.uint8)
|
||||
# dout = np.zeros((64 * 16, im.width), dtype=np.uint8)
|
||||
# for i in range (16):
|
||||
# print(f'Generating sprite row {i}/16...')
|
||||
# for j, (ox, oy, w, h, _ox, _oy, h1, h2) in enumerate(GROUND_SPRITES):
|
||||
# hn1 = 2. * (h1 + .5 - h / 2.) / h
|
||||
# hn2 = 2. * (h2 + .5 - h / 2.) / h
|
||||
# iflags = [i & (1 << k) for k in range(4)]
|
||||
# ut, uc, ub = i & 1, (i & 2) / 2, (i & 4) / 4
|
||||
# for y in range(0, h):
|
||||
# for x in range(0, w):
|
||||
# c = din[y + oy, x + ox]
|
||||
# if not c: continue
|
||||
# xn = 2. * (x + .5 - w / 2.) / w
|
||||
# yn = 2. * (y + .5 - h / 2.) / h
|
||||
# dn = math.hypot(xn, yn + 1)
|
||||
# ds = math.hypot(xn, yn - 1)
|
||||
# de = math.hypot(xn - 1, yn - hn1)
|
||||
# dw = math.hypot(xn + 1, yn - hn2)
|
||||
# f = min(d if fl else 2. for d, fl in zip([dn, de, ds, dw], iflags))
|
||||
# f = min(f * 1.44, 1.)
|
||||
# bc = spectra.rgb(f, 1, 0)
|
||||
# c = colors[c][1].blend(bc, ratio=0.2 + 0.1 * f)
|
||||
# dout[oy + y + 64 * i, ox + x] = find_best_color(c)
|
||||
# im2 = Image.fromarray(dout)
|
||||
# im2.putpalette(im.getpalette())
|
||||
# im2.save(os.path.join(DEST_DIR, "meadow_transitions.png"))
|
||||
|
||||
|
||||
dmask = np.vectorize(lambda x: 0 if x and x != 0xFF else 0xFF)(din).astype('uint8')
|
||||
immask = Image.fromarray(dmask, mode="L")
|
||||
# immask.save(os.path.join(DEST_DIR, "mask.png"))
|
||||
|
||||
dout = np.zeros((64 * 81, im.width), dtype=np.uint8)
|
||||
im2 = Image.fromarray(dout)
|
||||
im2.putpalette(im.getpalette())
|
||||
imd2 = ImageDraw.Draw(im2)
|
||||
# draw.line((0, 0) + im.size, fill=128)
|
||||
# draw.line((0, im.size[1], im.size[0], 0), fill=128)
|
||||
|
||||
def draw_bezier(imd, fill, width, a, b, c):
|
||||
N, M = 11, 2
|
||||
|
||||
lerp = lambda x, y, t: t * x + (N - t) * y
|
||||
lerp2m = lambda a, b, t: (lerp(a[0], (a[0] + b[0]) // 2, t), lerp(a[1], (a[1] + b[1]) // 2, t))
|
||||
lerp2 = lambda a, b, t: (lerp(a[0], b[0], t), lerp(a[1], b[1], t))
|
||||
|
||||
for t in range(-M, N + M + 1):
|
||||
p0 = lerp2m(a, b, t)
|
||||
p1 = lerp2m(c, b, N - t)
|
||||
pf = lerp2(p0, p1, t)
|
||||
x = pf[0] // N // N
|
||||
y = pf[1] // N // N
|
||||
imd.ellipse((x - width, y - width, x + width, y + width), fill=fill)
|
||||
|
||||
|
||||
for i in range (81):
|
||||
print(f'Generating rivers sprite row {i + 1}/81...')
|
||||
|
||||
points = (i % 3, (i // 3) % 3, (i // 9) % 3, (i // 27))
|
||||
inp = []
|
||||
outp = []
|
||||
for ii, p in enumerate(points):
|
||||
if p == 2: outp.append(ii)
|
||||
elif p == 1: inp.append(ii)
|
||||
|
||||
if not inp: inp = [4]
|
||||
if not outp: outp = [4]
|
||||
|
||||
for j, (ox, oy, w, h, _ox, _oy, h1, h2) in enumerate(GROUND_SPRITES):
|
||||
xx, yy = ox, oy + 64 * i
|
||||
|
||||
# # tile outline
|
||||
# corners = ((0, h1), (w / 2, 0), (w, h2), (w / 2, h))
|
||||
# for ii in range(len(corners)):
|
||||
# x1, y1 = corners[ii]
|
||||
# x2, y2 = corners[(ii + 1) % len(corners)]
|
||||
# imd2.line(((x1 + xx, y1 + yy), (x2 + xx, y2 + yy)), fill=0xA9, width=1)
|
||||
|
||||
# hn1 = 2. * (h1 + .5 - h / 2.) / h
|
||||
# hn2 = 2. * (h2 + .5 - h / 2.) / h
|
||||
wc = w // 4
|
||||
edges = ((wc, h1 / 2), (w - wc, h2 / 2), (w - wc, (h + h2) / 2), (wc, (h + h1) / 2), (w / 2, h / 2))
|
||||
center = (w / 2 + xx, h / 2 + yy)
|
||||
# if not inp:
|
||||
|
||||
# continue
|
||||
for ii in inp:
|
||||
for oo in outp:
|
||||
xy = ((edges[ii][0] + xx, edges[ii][1] + yy), (edges[oo][0] + xx, edges[oo][1] + yy))
|
||||
draw_bezier(imd2, 0x42, 4, xy[0], center, xy[1])
|
||||
for ii in inp:
|
||||
for oo in outp:
|
||||
xy = ((edges[ii][0] + xx, edges[ii][1] + yy), (edges[oo][0] + xx, edges[oo][1] + yy))
|
||||
draw_bezier(imd2, 0xF5, 3, xy[0], center, xy[1])
|
||||
|
||||
# # mark out
|
||||
# for oo in outp:
|
||||
# x, y = edges[oo][0] + xx, edges[oo][1] + yy
|
||||
# width = 3
|
||||
# imd2.ellipse((x - width, y - width, x + width, y + width), fill=0x42)
|
||||
|
||||
imd2.bitmap((0, i * 64), immask, fill=0)
|
||||
# iflags = [i & (1 << k) for k in range(4)]
|
||||
# ut, uc, ub = i & 1, (i & 2) / 2, (i & 4) / 4
|
||||
# for y in range(0, h):
|
||||
# for x in range(0, w):
|
||||
# c = din[y + oy, x + ox]
|
||||
# if not c: continue
|
||||
# xn = 2. * (x + .5 - w / 2.) / w
|
||||
# yn = 2. * (y + .5 - h / 2.) / h
|
||||
# dn = math.hypot(xn, yn + 1)
|
||||
# ds = math.hypot(xn, yn - 1)
|
||||
# de = math.hypot(xn - 1, yn - hn1)
|
||||
# dw = math.hypot(xn + 1, yn - hn2)
|
||||
# f = min(d if fl else 2. for d, fl in zip([dn, de, ds, dw], iflags))
|
||||
# f = min(f * 1.44, 1.)
|
||||
# bc = spectra.rgb(f, 1, 0)
|
||||
# c = colors[c][1].blend(bc, ratio=0.2 + 0.1 * f)
|
||||
# dout[oy + y + 64 * i, ox + x] = find_best_color(c)
|
||||
# im2 = Image.fromarray(dout)
|
||||
# im2.putpalette(im.getpalette())
|
||||
im2.save(os.path.join(DEST_DIR, "rivers.png"))
|
||||
BIN
grf/alpine/gfx/bare03_grid.gimp.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
grf/alpine/gfx/bare13_grid_temperate.gimp.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
grf/alpine/gfx/bare23_grid_temperate.gimp.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
grf/alpine/gfx/grass_grid_temperate.gimp.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
grf/alpine/gfx/half_meadow_grid_temperate.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
grf/alpine/gfx/mask.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
grf/alpine/gfx/meadow_grid_temperate.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
grf/alpine/gfx/meadow_transitions.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
grf/alpine/gfx/rivers.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
grf/alpine/gfx/rocks_grid_temperate.gimp.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
grf/alpine/gfx/rough_grid_temperate.gimp.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
grf/alpine/gfx/snow14_grid_alpine.gimp.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
grf/alpine/gfx/snow24_grid_alpine.gimp.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
grf/alpine/gfx/snow34_grid_alpine.gimp.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
grf/alpine/gfx/snow_grid.gimp.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
grf/alpine/gfx/water/seashore_grid_temperate.gimp.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
336
grf/alpine/grf.py
Normal file
@@ -0,0 +1,336 @@
|
||||
import math
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from nml.spriteencoder import SpriteEncoder
|
||||
import spectra
|
||||
import struct
|
||||
import numpy as np
|
||||
|
||||
to_spectra = lambda r, g, b: spectra.rgb(float(r) / 255., float(g) / 255., float(b) / 255.)
|
||||
# working with DOS palette only
|
||||
PALETTE = (0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 255, 255, 255)
|
||||
SAFE_COLORS = set(range(1, 0xD7))
|
||||
ALL_COLORS = set(range(256))
|
||||
SPECTRA_PALETTE = {i:to_spectra(PALETTE[i * 3], PALETTE[i * 3 + 1], PALETTE[i * 3 + 2]) for i in range(256)}
|
||||
WATER_COLORS = set(range(0xF5, 0xFF))
|
||||
|
||||
# ZOOM_OUT_4X, ZOOM_NORMAL, ZOOM_OUT_2X, ZOOM_OUT_8X, ZOOM_OUT_16X, ZOOM_OUT_32X = range(6)
|
||||
ZOOM_4X, ZOOM_NORMAL, ZOOM_2X, ZOOM_8X, ZOOM_16X, ZOOM_32X = range(6)
|
||||
BPP_8, BPP_32 = range(2)
|
||||
|
||||
def color_distance(c1, c2):
|
||||
rmean = (c1.rgb[0] + c2.rgb[0]) / 2.
|
||||
r = c1.rgb[0] - c2.rgb[0]
|
||||
g = c1.rgb[1] - c2.rgb[1]
|
||||
b = c1.rgb[2] - c2.rgb[2]
|
||||
return math.sqrt(
|
||||
((2 + rmean) * r * r) +
|
||||
4 * g * g +
|
||||
(3 - rmean) * b * b)
|
||||
|
||||
|
||||
def find_best_color(x, in_range=SAFE_COLORS):
|
||||
mj, md = 0, 1e100
|
||||
for j in in_range:
|
||||
c = SPECTRA_PALETTE[j]
|
||||
d = color_distance(x, c)
|
||||
if d < md:
|
||||
mj, md = j, d
|
||||
return mj
|
||||
|
||||
|
||||
# def map_rgb_image(self, im):
|
||||
# assert im.mode == 'RGB', im.mode
|
||||
# data = np.array(im)
|
||||
|
||||
|
||||
class BaseSprite:
|
||||
def get_data(self):
|
||||
raise NotImplemented
|
||||
|
||||
def get_data_size(self):
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
class PaletteRemap(BaseSprite):
|
||||
def __init__(self, ranges=None):
|
||||
self.remap = np.arange(256, dtype=np.uint8)
|
||||
if ranges:
|
||||
self.set_ranges(ranges)
|
||||
|
||||
def get_data(self):
|
||||
return b'\x00' + self.remap.tobytes()
|
||||
|
||||
def get_data_size(self):
|
||||
return 257
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, color_func, remap_water=False):
|
||||
res = cls()
|
||||
for i in SAFE_COLORS:
|
||||
res.remap[i] = find_best_color(color_func(SPECTRA_PALETTE[i]))
|
||||
if remap_water:
|
||||
for i in WATER_COLORS:
|
||||
res.remap[i] = find_best_color(color_func(SPECTRA_PALETTE[i]))
|
||||
return res
|
||||
|
||||
def set_ranges(self, ranges):
|
||||
for r in ranges:
|
||||
f, t, v = r
|
||||
self.remap[f: t + 1] = v
|
||||
|
||||
def remap_image(self, im):
|
||||
assert im.mode == 'P', im.mode
|
||||
data = np.array(im)
|
||||
data = self.remap[data]
|
||||
res = Image.fromarray(data)
|
||||
res.putpalette(PALETTE)
|
||||
return res
|
||||
|
||||
|
||||
class RealSprite(BaseSprite):
|
||||
def __init__(self, w, h, *, xofs=0, yofs=0, zoom=ZOOM_4X):
|
||||
self.sprite_id = None
|
||||
self.w = w
|
||||
self.h = h
|
||||
# self.file = None
|
||||
# self.x = None
|
||||
# self.y = None
|
||||
self.xofs = xofs
|
||||
self.yofs = yofs
|
||||
self.zoom = zoom
|
||||
|
||||
def get_data_size(self):
|
||||
return 4
|
||||
|
||||
def get_data(self):
|
||||
return struct.pack('<I', self.sprite_id)
|
||||
|
||||
def get_real_data(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def draw(self, img):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ImageFile:
|
||||
def __init__(self, filename, bpp=BPP_8):
|
||||
assert(bpp == BPP_8) # TODO
|
||||
self.filename = filename
|
||||
self.bpp = bpp
|
||||
self._image = None
|
||||
|
||||
def get_image(self):
|
||||
if self._image:
|
||||
return self._image
|
||||
img = Image.open(self.filename)
|
||||
assert (img.mode == 'P') # TODO
|
||||
pal = tuple(img.getpalette())
|
||||
if pal != PALETTE:
|
||||
print(f'Custom palette in file {self.filename}, converting...')
|
||||
# for i in range(256):
|
||||
# if tuple(pal[i * 3: i*3 + 3]) != PALETTE[i * 3: i*3 + 3]:
|
||||
# print(i, pal[i * 3: i*3 + 3], PALETTE[i * 3: i*3 + 3])
|
||||
remap = PaletteRemap()
|
||||
for i in ALL_COLORS:
|
||||
remap.remap[i] = find_best_color(to_spectra(pal[3 * i], pal[3 * i + 1], pal[3 * i + 2]), in_range=ALL_COLORS)
|
||||
self._image = remap.remap_image(img)
|
||||
else:
|
||||
self._image = img
|
||||
return self._image
|
||||
|
||||
|
||||
class FileSprite(RealSprite):
|
||||
def __init__(self, file, x, y, w, h, **kw):
|
||||
assert(isinstance(file, ImageFile))
|
||||
super().__init__(w, h, **kw)
|
||||
self.file = file
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def get_real_data(self):
|
||||
img = self.file.get_image()
|
||||
img = img.crop((self.x, self.y, self.x + self.w, self.y + self.h))
|
||||
raw_data = img.tobytes()
|
||||
se = SpriteEncoder(True, False, None)
|
||||
data = se.sprite_compress(raw_data)
|
||||
return struct.pack(
|
||||
'<IIBBHHhh',
|
||||
self.sprite_id,
|
||||
len(data) + 10, 0x04,
|
||||
self.zoom,
|
||||
self.h,
|
||||
self.w,
|
||||
self.xofs,
|
||||
self.yofs,
|
||||
) + data
|
||||
# return struct.pack(
|
||||
# '<IIBBHHhhI',
|
||||
# self.sprite_id,
|
||||
# len(data) + 14, 0x0C,
|
||||
# self.zoom,
|
||||
# self.h,
|
||||
# self.w,
|
||||
# self.xofs,
|
||||
# self.yofs,
|
||||
# len(raw_data),
|
||||
# ) + data
|
||||
|
||||
|
||||
|
||||
class SpriteSheet:
|
||||
def __init__(self, sprites=None):
|
||||
self._sprites = list(sprites) if sprites else []
|
||||
|
||||
def make_image(self, filename, padding=5, columns=10):
|
||||
w, h = 0, padding
|
||||
lineofs = []
|
||||
for i in range(0, len(self._sprites), columns):
|
||||
w = max(w, sum(s.w for s in self._sprites[i: i + columns]))
|
||||
lineofs.append(h)
|
||||
h += padding + max((s.h for s in self._sprites[i: i + columns]), default=0)
|
||||
|
||||
w += (columns + 1) * padding
|
||||
im = Image.new('L', (w, h), color=0xff)
|
||||
im.putpalette(PALETTE)
|
||||
|
||||
x = 0
|
||||
for i, s in enumerate(self._sprites):
|
||||
y = lineofs[i // columns]
|
||||
if i % columns == 0:
|
||||
x = padding
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.file = filename
|
||||
s.draw(im)
|
||||
x += s.w + padding
|
||||
|
||||
im.save(filename)
|
||||
|
||||
|
||||
def write_nml(self, file):
|
||||
for s in self._sprites:
|
||||
file.write(s.get_nml())
|
||||
file.write('\n')
|
||||
file.write('\n')
|
||||
|
||||
|
||||
class DummySprite(BaseSprite):
|
||||
def get_data(self):
|
||||
return b'\x00'
|
||||
|
||||
def get_data_size(self):
|
||||
return 1
|
||||
|
||||
|
||||
class DescriptionSprite(BaseSprite): # action 8
|
||||
def __init__(self, grfid, name, description):
|
||||
assert isinstance(grfid, bytes)
|
||||
assert isinstance(name, str)
|
||||
assert isinstance(description, str)
|
||||
self.grfid = grfid
|
||||
self.name = name
|
||||
self.description = description
|
||||
self._data = b'\x08\x08' + self.grfid + self.name.encode('utf-8') + b'\x00' + self.description.encode('utf-8') + b'\x00'
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
def get_data_size(self):
|
||||
return len(self._data)
|
||||
|
||||
|
||||
class InformationSprite(BaseSprite): # action 14
|
||||
def __init__(self, palette): # TODO everything else
|
||||
# self.palette = {'D': b'\x00', 'W': b'\x01', 'A': b'\x02'}[palette]
|
||||
self.palette = palette.encode('utf-8')
|
||||
self._data = b'\x14CINFOBPALS\x01\x00' + self.palette + b'\x00\x00'
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
def get_data_size(self):
|
||||
return len(self._data)
|
||||
|
||||
|
||||
class ReplaceSprites: # action A
|
||||
def __init__(self, sets):
|
||||
assert isinstance(sets, (list, tuple))
|
||||
assert len(sets) <= 0xff
|
||||
for first, num in sets:
|
||||
assert isinstance(first, int)
|
||||
assert isinstance(num, int)
|
||||
|
||||
self.sets = sets
|
||||
|
||||
def get_data(self):
|
||||
return bytes((0xa, len(self.sets))) + b''.join(struct.pack('<BH', num, first) for first, num in self.sets)
|
||||
|
||||
def get_data_size(self):
|
||||
return 2 + 3 * len(self.sets)
|
||||
|
||||
|
||||
class ReplaceNewSprites: # action 5
|
||||
def __init__(self, set_type, num): # TODO offset
|
||||
assert isinstance(set_type, int)
|
||||
assert isinstance(num, int)
|
||||
self.set_type = set_type
|
||||
self.num = num
|
||||
|
||||
def get_data(self):
|
||||
return bytes((0x5,)) + struct.pack('<BBH', self.set_type, 0xff, self.num)
|
||||
|
||||
def get_data_size(self):
|
||||
return 5
|
||||
|
||||
|
||||
class NewGRF:
|
||||
def __init__(self, grfid, name, description):
|
||||
self.sprites = []
|
||||
self.pseudo_sprites = []
|
||||
self.pseudo_sprites.append(InformationSprite('D'))
|
||||
self.pseudo_sprites.append(DescriptionSprite(grfid, name, description))
|
||||
self._next_sprite_id = 1
|
||||
|
||||
def add_sprite(self, *sprites):
|
||||
assert(len(sprites) > 0)
|
||||
if isinstance(sprites[0], RealSprite):
|
||||
assert(all(isinstance(s, RealSprite) for s in sprites))
|
||||
assert(len(set(s.zoom for s in sprites)) == len(sprites))
|
||||
for s in sprites:
|
||||
s.sprite_id = self._next_sprite_id
|
||||
self._next_sprite_id += 1
|
||||
|
||||
for s in sprites:
|
||||
self.sprites.append(s)
|
||||
|
||||
self.pseudo_sprites.append(sprites[0])
|
||||
else:
|
||||
assert(len(sprites) == 1)
|
||||
self.pseudo_sprites.append(sprites[0])
|
||||
|
||||
def _write_pseudo_sprite(self, f, data, grf_type=0xff):
|
||||
f.write(struct.pack('<IB', len(data), grf_type))
|
||||
f.write(data)
|
||||
|
||||
def write(self, filename):
|
||||
data_offset = 14
|
||||
for s in self.pseudo_sprites:
|
||||
data_offset += s.get_data_size() + 5
|
||||
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(b'\x00\x00GRF\x82\x0d\x0a\x1a\x0a') # file header
|
||||
f.write(struct.pack('<I', data_offset))
|
||||
f.write(b'\x00') # compression(1)
|
||||
# f.write(b'\x04\x00\x00\x00') # num(4)
|
||||
# f.write(b'\xFF') # grf_type(1)
|
||||
# f.write(b'\xb0\x01\x00\x00') # num + 0xff -> recoloursprites() (257 each)
|
||||
self._write_pseudo_sprite(f, b'\x02\x00\x00\x00')
|
||||
|
||||
for s in self.pseudo_sprites:
|
||||
self._write_pseudo_sprite(f, s.get_data(), grf_type=0xfd if isinstance(s, RealSprite) else 0xff)
|
||||
f.write(b'\x00\x00\x00\x00')
|
||||
for s in self.sprites:
|
||||
f.write(s.get_real_data())
|
||||
|
||||
f.write(b'\x00\x00\x00\x00')
|
||||
8
grf/alpine/lang/english.lng
Normal file
@@ -0,0 +1,8 @@
|
||||
##grflangid 0x01
|
||||
STR_GRF_NAME :CityMania Alpine Landscape
|
||||
STR_GRF_DESCRIPTION :Modified OpenGFX spritess for alpine climate
|
||||
STR_TEST_OBJECT : CM Test object
|
||||
STR_FLMA : Flower Meadow
|
||||
STR_TREE : Tree
|
||||
STR_CREEK : Creek
|
||||
STR_MINEFIELD : Minefield
|
||||
560
grf/alpine/readgrftest.py
Normal file
@@ -0,0 +1,560 @@
|
||||
import sys
|
||||
import struct
|
||||
from nml import lz77
|
||||
|
||||
|
||||
def hex_str(s):
|
||||
if isinstance(s, (bytes, memoryview)):
|
||||
return ':'.join('{:02x}'.format(b) for b in s)
|
||||
return ':'.join('{:02x}'.format(ord(c)) for c in s)
|
||||
|
||||
|
||||
def read_extended_byte(data, offset):
|
||||
res = data[offset]
|
||||
if res != 0xff:
|
||||
return res, offset + 1
|
||||
return data[offset + 1] | (data[offset + 2] << 8), offset + 3
|
||||
|
||||
|
||||
def read_dword(data, offset):
|
||||
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24), offset + 4
|
||||
|
||||
|
||||
FEATURES = {
|
||||
0: 'Train',
|
||||
1: 'RV',
|
||||
2: 'Ship',
|
||||
3: 'Aircraft',
|
||||
4: 'Station',
|
||||
5: 'Canal',
|
||||
6: 'Bridge',
|
||||
7: 'House',
|
||||
8: 'Setting',
|
||||
9: 'IndTiles',
|
||||
0xa: 'Industry',
|
||||
0xb: 'Cargo',
|
||||
0xc: 'Sound',
|
||||
0xd: 'Airport',
|
||||
0xe: '?Signals?',
|
||||
0xf: 'Object',
|
||||
0x10: 'Railtype',
|
||||
0x11: 'AirportTiles',
|
||||
0x12: 'Roadtype',
|
||||
0x13: 'Tramtype',
|
||||
}
|
||||
|
||||
ACTION0_TRAIN_PROPS = {
|
||||
0x05: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Track type (see below) should be same as front
|
||||
0x08: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 AI special flag: set to 1 if engine is 'optimized' for passenger service (AI won't use it for other cargo), 0 otherwise no
|
||||
0x09: 'W', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Speed in mph*1.6 (see below) no
|
||||
0x0B: 'W', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Power (0 for wagons) should be zero
|
||||
0x0D: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Running cost factor (0 for wagons) should be zero
|
||||
0x0E: 'D', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Running cost base, see below should be zero
|
||||
0x12: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Sprite ID (FD for new graphics) yes
|
||||
0x13: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Dual-headed flag; 1 if dual-headed engine, 0 otherwise should be zero also for front
|
||||
0x14: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Cargo capacity yes
|
||||
0x15: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Cargo type, see CargoTypes
|
||||
0x16: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Weight in tons should be zero
|
||||
0x17: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 Cost factor should be zero
|
||||
0x18: 'B', # Supported by OpenTTD <0.7<0.7 Supported by TTDPatch 2.02.0[1] Engine rank for the AI (AI selects the highest-rank engine of those it can buy) no
|
||||
0x19: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 GRFv≥1 Engine traction type (see below) no
|
||||
0x1A: 'B*', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 GRFv≥1 Not a property, but an action: sort the purchase list. no
|
||||
0x1B: 'W', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 GRFv≥6 Power added by each wagon connected to this engine, see below should be zero
|
||||
0x1C: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 GRFv≥6 Refit cost, using 50% of the purchase price cost base yes
|
||||
0x1D: 'D', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 GRFv≥6 Bit mask of cargo types available for refitting, see column 2 (bit value) in CargoTypes yes
|
||||
0x1E: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 GRFv≥6 Callback flags bit mask, see below yes
|
||||
0x1F: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 19)2.5 Coefficient of tractive effort should be zero
|
||||
0x20: 'B', # Supported by OpenTTD 1.11.1 Supported by TTDPatch 2.5 (alpha 27)2.5 Coefficient of air drag should be zero
|
||||
0x21: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.02.0 GRFv≥2 Make vehicle shorter by this amount, see below yes
|
||||
0x22: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 GRFv≥6 Set visual effect type (steam/smoke/sparks) as well as position, see below yes
|
||||
0x23: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 GRFv≥6 Set how much weight is added by making wagons powered (i.e. weight of engine), see below should be zero
|
||||
0x24: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 44)2.5 High byte of vehicle weight, weight will be prop.24*256+prop.16 should be zero
|
||||
0x25: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 44)2.5 User-defined bit mask to set when checking veh. var. 42 yes
|
||||
0x26: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 44)2.5 Retire vehicle early, this many years before the end of phase 2 (see Action0General) no
|
||||
0x27: 'B', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 58)2.5 Miscellaneous flags partly
|
||||
0x28: 'W', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 58)2.5 Refittable cargo classes yes
|
||||
0x29: 'W', # Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.5 (alpha 58)2.5 Non-refittable cargo classes yes
|
||||
0x2A: 'D', # Supported by OpenTTD 0.6 (r7191)0.6 Supported by TTDPatch 2.5 (r1210)2.5 Long format introduction date no
|
||||
0x2B: 'W', # Supported by OpenTTD 1.2 (r22713)1.2 Not supported by TTDPatch Custom cargo ageing period yes
|
||||
0x2C: 'n*B', # Supported by OpenTTD 1.2 (r23291)1.2 Not supported by TTDPatch List of always refittable cargo types yes
|
||||
0x2D: 'n*B', # Supported by OpenTTD 1.2 (r23291)1.2 Not supported by TTDPatch List of never refittable cargo types yes
|
||||
0x2E: 'W', # Supported by OpenTTD 12 (g2183fd4dab)12 Not supported by TTDPatch Maximum curve speed modifier yes
|
||||
}
|
||||
|
||||
ACTION0_OBJECT_PROPS = {
|
||||
0x08: ('label', 'L'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Class label, see below
|
||||
0x09: ('class_name_id', 'W'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Text ID for class
|
||||
0x0A: ('name_id', 'W'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Text ID for this object
|
||||
0x0B: ('climate', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Climate availability
|
||||
0x0C: ('size', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Byte representing size, see below
|
||||
0x0D: ('build_cost_factor', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Object build cost factor (sets object removal cost factor as well)
|
||||
0x0E: ('intro_date', 'D'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Introduction date, see below
|
||||
0x0F: ('eol_date', 'D'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 End of life date, see below
|
||||
0x10: ('flags', 'W'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Object flags, see below
|
||||
0x11: ('anim_info', 'W'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Animation information
|
||||
0x12: ('anim_speed', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Animation speed
|
||||
0x13: ('anim_trigger', 'W'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Animation triggers
|
||||
0x14: ('removal_cost_factor', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Object removal cost factor (set after object build cost factor)
|
||||
0x15: ('cb_flags', 'W'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Callback flags, see below
|
||||
0x16: ('building_height', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Height of the building
|
||||
0x17: ('num_views', 'B'), # Supported by OpenTTD 1.1 (r20670)1.1 Supported by TTDPatch 2.6 (r2340)2.6 Number of object views
|
||||
0x18: ('num_objects', 'B'), # Supported by OpenTTD 1.4 (r25879)1.4 Not supported by TTDPatch Measure for number of objects placed upon map creation
|
||||
}
|
||||
|
||||
ACTION0_PROPS = {
|
||||
0: ACTION0_TRAIN_PROPS,
|
||||
0xf: ACTION0_OBJECT_PROPS,
|
||||
}
|
||||
|
||||
|
||||
def str_feature(feature):
|
||||
return f'{FEATURES[feature]}<{feature:02x}>'
|
||||
|
||||
|
||||
def str_sprite(sprite):
|
||||
sprite_id = sprite & 0x1fff
|
||||
draw = {0: 'N', 1 : 'T', 2: 'R'}[(sprite >> 14) & 3]
|
||||
color_translation = (sprite >> 16) & 0x3fff
|
||||
normal_in_transparent = bool(sprite & (1 << 30))
|
||||
sprite_type = sprite >> 31
|
||||
ntstr = ['', ' NF'][normal_in_transparent]
|
||||
return f'[{sprite_id} {draw}{ntstr} {color_translation}-{sprite_type}]'
|
||||
|
||||
|
||||
def read_property(data, ofs, fmt):
|
||||
if fmt == 'B':
|
||||
return data[ofs], ofs + 1
|
||||
|
||||
if fmt == 'W':
|
||||
return data[ofs] | (data[ofs + 1] << 8), ofs + 2
|
||||
|
||||
if fmt == 'L':
|
||||
return data[ofs: ofs + 4], ofs + 4
|
||||
|
||||
if fmt == 'D':
|
||||
return read_dword(data, ofs)
|
||||
|
||||
if fmt == 'B*':
|
||||
return read_extended_byte(data, ofs)
|
||||
|
||||
if fmt == 'n*B':
|
||||
n = data[ofs]
|
||||
return data[ofs + 1: ofs + 1 + n], ofs + 1 + n
|
||||
|
||||
assert False, fmt
|
||||
|
||||
|
||||
def decode_action0(data):
|
||||
num = data[0]
|
||||
feature = data[0]
|
||||
num_props = data[1]
|
||||
num_info = data[2]
|
||||
first_id, ofs = read_extended_byte(data, 3)
|
||||
props = {}
|
||||
for _ in range(num_props):
|
||||
prop = data[ofs]
|
||||
propdict = ACTION0_PROPS[feature]
|
||||
name, fmt = propdict[prop]
|
||||
value, ofs = read_property(data, ofs + 1, fmt)
|
||||
key = f'{name}<{prop:02x}>'
|
||||
assert key not in props, key
|
||||
props[key] = value
|
||||
|
||||
print(f' <0>FEATURE feature:{str_feature(feature)} num_info:{num_info} first_id:{first_id} props:{props}')
|
||||
|
||||
|
||||
def decode_action1(data):
|
||||
num = data[0]
|
||||
feature = data[0]
|
||||
num_sets = data[1]
|
||||
num_ent, _ = read_extended_byte(data, 2)
|
||||
print(f' <1>SPRITESET feature:{str_feature(feature)} num_sets:{num_sets} num_ent:{num_ent}')
|
||||
|
||||
|
||||
SPRITE_GROUP_OP = [
|
||||
'ADD', # a + b
|
||||
'SUB', # a - b
|
||||
'SMIN', # (signed) min(a, b)
|
||||
'SMAX', # (signed) max(a, b)
|
||||
'UMIN', # (unsigned) min(a, b)
|
||||
'UMAX', # (unsigned) max(a, b)
|
||||
'SDIV', # (signed) a / b
|
||||
'SMOD', # (signed) a % b
|
||||
'UDIV', # (unsigned) a / b
|
||||
'UMOD', # (unsigned) a & b
|
||||
'MUL', # a * b
|
||||
'AND', # a & b
|
||||
'OR', # a | b
|
||||
'XOR', # a ^ b
|
||||
'STO', # store a into temporary storage, indexed by b. return a
|
||||
'RST', # return b
|
||||
'STOP', # store a into persistent storage, indexed by b, return a
|
||||
'ROR', # rotate a b positions to the right
|
||||
'SCMP', # (signed) comparison (a < b -> 0, a == b = 1, a > b = 2)
|
||||
'UCMP', # (unsigned) comparison (a < b -> 0, a == b = 1, a > b = 2)
|
||||
'SHL', # a << b
|
||||
'SHR', # (unsigned) a >> b
|
||||
'SAR', # (signed) a >> b
|
||||
]
|
||||
|
||||
class DataReader:
|
||||
def __init__(self, data, offset=0):
|
||||
self.data = data
|
||||
self.offset = offset
|
||||
|
||||
def get_byte(self):
|
||||
self.offset += 1
|
||||
return self.data[self.offset - 1]
|
||||
|
||||
def get_extended_byte(self):
|
||||
res, self.offset = read_extended_byte(self.data, self.offset)
|
||||
return res
|
||||
|
||||
def get_word(self):
|
||||
return self.get_byte() | (self.get_byte() << 8)
|
||||
|
||||
def get_var(self, n):
|
||||
size = 1 << n
|
||||
res = struct.unpack_from({0: '<B', 1: '<H', 2: '<I'}[n], self.data, offset=self.offset)[0]
|
||||
self.offset += size
|
||||
return res
|
||||
|
||||
|
||||
TLF_NOTHING = 0x00
|
||||
TLF_DODRAW = 0x01 # Only draw sprite if value of register TileLayoutRegisters::dodraw is non-zero.
|
||||
TLF_SPRITE = 0x02 # Add signed offset to sprite from register TileLayoutRegisters::sprite.
|
||||
TLF_PALETTE = 0x04 # Add signed offset to palette from register TileLayoutRegisters::palette.
|
||||
TLF_CUSTOM_PALETTE = 0x08 # Palette is from Action 1 (moved to SPRITE_MODIFIER_CUSTOM_SPRITE in palette during loading).
|
||||
TLF_BB_XY_OFFSET = 0x10 # Add signed offset to bounding box X and Y positions from register TileLayoutRegisters::delta.parent[0..1].
|
||||
TLF_BB_Z_OFFSET = 0x20 # Add signed offset to bounding box Z positions from register TileLayoutRegisters::delta.parent[2].
|
||||
TLF_CHILD_X_OFFSET = 0x10 # Add signed offset to child sprite X positions from register TileLayoutRegisters::delta.child[0].
|
||||
TLF_CHILD_Y_OFFSET = 0x20 # Add signed offset to child sprite Y positions from register TileLayoutRegisters::delta.child[1].
|
||||
TLF_SPRITE_VAR10 = 0x40 # Resolve sprite with a specific value in variable 10.
|
||||
TLF_PALETTE_VAR10 = 0x80 # Resolve palette with a specific value in variable 10.
|
||||
TLF_KNOWN_FLAGS = 0xFF # Known flags. Any unknown set flag will disable the GRF.
|
||||
|
||||
# /** Flags which are still required after loading the GRF. */
|
||||
TLF_DRAWING_FLAGS = ~TLF_CUSTOM_PALETTE
|
||||
|
||||
# /** Flags which do not work for the (first) ground sprite. */
|
||||
TLF_NON_GROUND_FLAGS = TLF_BB_XY_OFFSET | TLF_BB_Z_OFFSET | TLF_CHILD_X_OFFSET | TLF_CHILD_Y_OFFSET
|
||||
|
||||
# /** Flags which refer to using multiple action-1-2-3 chains. */
|
||||
TLF_VAR10_FLAGS = TLF_SPRITE_VAR10 | TLF_PALETTE_VAR10
|
||||
|
||||
# /** Flags which require resolving the action-1-2-3 chain for the sprite, even if it is no action-1 sprite. */
|
||||
TLF_SPRITE_REG_FLAGS = TLF_DODRAW | TLF_SPRITE | TLF_BB_XY_OFFSET | TLF_BB_Z_OFFSET | TLF_CHILD_X_OFFSET | TLF_CHILD_Y_OFFSET
|
||||
|
||||
# /** Flags which require resolving the action-1-2-3 chain for the palette, even if it is no action-1 palette. */
|
||||
TLF_PALETTE_REG_FLAGS = TLF_PALETTE
|
||||
|
||||
|
||||
def read_sprite_layout_registers(d, flags, is_parent):
|
||||
regs = {'flags': flags & TLF_DRAWING_FLAGS}
|
||||
if flags & TLF_DODRAW: regs['dodraw'] = d.get_byte();
|
||||
if flags & TLF_SPRITE: regs['sprite'] = d.get_byte();
|
||||
if flags & TLF_PALETTE: regs['palette'] = d.get_byte();
|
||||
|
||||
if is_parent:
|
||||
delta = [d.get_byte(), d.get_byte(), 0] if flags & TLF_BB_XY_OFFSET else [0, 0, 0]
|
||||
if flags & TLF_BB_Z_OFFSET: delta[2] = d.get_byte()
|
||||
regs['delta_parent'] = tuple(delta)
|
||||
else:
|
||||
delta, delta_set = [0, 0], False
|
||||
if flags & TLF_CHILD_X_OFFSET: delta[0], delta_set = d.get_byte(), True
|
||||
if flags & TLF_CHILD_Y_OFFSET: delta[1], delta_set = d.get_byte(), True
|
||||
if delta_set: regs['delta_child'] = tuple(delta)
|
||||
|
||||
if flags & TLF_SPRITE_VAR10: regs['sprite_var10'] = d.get_byte()
|
||||
if flags & TLF_PALETTE_VAR10: regs['palette_var10'] = d.get_byte()
|
||||
return regs
|
||||
|
||||
|
||||
def read_sprite_layout(d, num, no_z_position):
|
||||
has_z_position = not no_z_position
|
||||
has_flags = bool((num >> 6) & 1)
|
||||
num &= 0x3f
|
||||
|
||||
def read_sprite():
|
||||
sprite = d.get_word()
|
||||
pal = d.get_word()
|
||||
flags = d.get_word() if has_flags else TLF_NOTHING
|
||||
return {'sprite': sprite, 'pal': pal, 'flags': flags}
|
||||
|
||||
ground = read_sprite()
|
||||
ground_regs = read_sprite_layout_registers(d, ground['flags'], False)
|
||||
sprites = []
|
||||
for _ in range(num):
|
||||
seq = {}
|
||||
seq['sprite'] = read_sprite()
|
||||
delta = seq['delta'] = (d.get_byte(), d.get_byte(), d.get_byte() if has_z_position else 0)
|
||||
is_parent = (delta[2] != 0x80)
|
||||
if is_parent:
|
||||
seq['size'] = (d.get_byte(), d.get_byte(), d.get_byte())
|
||||
seq['regs'] = read_sprite_layout_registers(d, seq['sprite']['flags'], is_parent)
|
||||
sprites.append(seq)
|
||||
|
||||
return {
|
||||
'ground': {
|
||||
'sprite': ground,
|
||||
'regs': ground_regs,
|
||||
},
|
||||
'sprites': sprites
|
||||
}
|
||||
|
||||
def decode_action2(data):
|
||||
feature = data[0]
|
||||
set_id = data[1]
|
||||
num_ent1 = data[2]
|
||||
d = DataReader(data, 3)
|
||||
|
||||
print(f' <2>SPRITEGROUP feature:{str_feature(feature)} set_id:{set_id} ', end='')
|
||||
|
||||
if feature in (0x07, 0x09, 0x0f, 0x11):
|
||||
if num_ent1 == 0:
|
||||
ground_sprite, building_sprite, xofs, yofs, xext, yext, zext = struct.unpack_from('<IIBBBBB', data, offset=3)
|
||||
ground_sprite = str_sprite(ground_sprite)
|
||||
building_sprite = str_sprite(building_sprite)
|
||||
print(f'ground_sprite:{ground_sprite} building_sprite:{building_sprite} '
|
||||
f'xofs:{xofs} yofs:{yofs} extent:({xext}, {yext}, {zext})')
|
||||
return
|
||||
if num_ent1 < 0x3f:
|
||||
raise NotImplemented
|
||||
|
||||
if num_ent1 in (0x81, 0x82, 0x85, 0x86, 0x89, 0x8a):
|
||||
group_size = (num_ent1 >> 2) & 3
|
||||
first = True
|
||||
ofs = 3
|
||||
adjusts = []
|
||||
while True:
|
||||
res = {}
|
||||
res['op'] = 0 if first else d.get_byte()
|
||||
var = res['var'] = d.get_byte()
|
||||
if var == 0x7e:
|
||||
res['subroutine'] = d.get_byte()
|
||||
else:
|
||||
res['parameter'] = d.get_byte() if 0x60 <= var < 0x80 else 0
|
||||
varadj = d.get_byte()
|
||||
res['shift_num'] = varadj & 0x1f
|
||||
has_more = bool(varadj & 0x20)
|
||||
res['type'] = varadj >> 6
|
||||
res['and_mask'] = d.get_var(group_size)
|
||||
if res['type'] != 0:
|
||||
res['add_val'] = d.get_var(group_size)
|
||||
res['divmod_val'] = d.get_var(group_size)
|
||||
adjusts.append(res)
|
||||
|
||||
if not has_more:
|
||||
break
|
||||
|
||||
n_ranges = d.get_byte()
|
||||
ranges = []
|
||||
for _ in range(n_ranges):
|
||||
group = d.get_word()
|
||||
low = d.get_var(group_size)
|
||||
high = d.get_var(group_size)
|
||||
ranges.append((group, low, high))
|
||||
|
||||
default_group = d.get_word()
|
||||
|
||||
print(f'default_group: {default_group} adjusts:{adjusts} ranges:{ranges} ')
|
||||
|
||||
return
|
||||
|
||||
layout = read_sprite_layout(d, max(num_ent1, 1), num_ent1 == 0)
|
||||
print(f'layout:{layout}')
|
||||
return
|
||||
# num_loaded = num_ent1
|
||||
# num_loading = get_byte()
|
||||
# [get_word() for i in range(num_loaded)]
|
||||
# [get_word() for i in range(num_loading)]
|
||||
# assert False, num_ent1
|
||||
# # assert num_ent1 < 0x3f + 0x40, num_ent1
|
||||
# return
|
||||
|
||||
num_ent2 = data[3]
|
||||
ent1 = struct.unpack_from('<' + 'H' * num_ent1, data, offset=4)
|
||||
ent2 = struct.unpack_from('<' + 'H' * num_ent2, data, offset=4 + 2 * num_ent1)
|
||||
print(f'ent1:{ent1} ent2:{ent2}')
|
||||
|
||||
|
||||
def decode_action4(data):
|
||||
fmt = '<BBB' + ('H' if data[1] & 0xf0 else 'B')
|
||||
feature, lang, num, offset = struct.unpack_from(fmt, data)
|
||||
strings = [s.decode('utf-8') for s in data[struct.calcsize(fmt):].split(b'\0')[:-1]]
|
||||
print(f' <4>STRINGS feature:{str_feature(feature)} lang:{lang} num:{num} offset:{offset} strings:{strings}')
|
||||
|
||||
|
||||
def decode_action5(data):
|
||||
t = data[0]
|
||||
offset = 0
|
||||
num, dataofs = read_extended_byte(data, 1)
|
||||
if t & 0xf0:
|
||||
offset, _ = read_extended_byte(data, dataofs)
|
||||
t &= ~0xf0
|
||||
print(f' <5>REPLACENEW type:{t} num:{num}, offset:{offset}')
|
||||
|
||||
|
||||
def decode_action6(data):
|
||||
d = DataReader(data, 0)
|
||||
params = []
|
||||
while True:
|
||||
param_num = d.get_byte()
|
||||
if param_num == 0xFF:
|
||||
break
|
||||
param_size = d.get_byte()
|
||||
offset = d.get_extended_byte()
|
||||
params.append({'num': param_num, 'size': param_size, 'offset': offset})
|
||||
print(f' <6>EDITPARAM params:{params}')
|
||||
|
||||
|
||||
def decode_actionA(data):
|
||||
num = data[0]
|
||||
sets = [struct.unpack_from('<BH', data, offset=3*i + 1) for i in range(num)]
|
||||
print(f' <A>REPLACEBASE sets:<{num}>{sets}')
|
||||
|
||||
|
||||
OPERATIONS = {
|
||||
0x00: '{target} = {source1}', # Supported by OpenTTD Supported by TTDPatch Assignment target = source1
|
||||
0x01: '{target} = {source1} + {source2}', # Supported by OpenTTD Supported by TTDPatch Addition target = source1 + source2
|
||||
0x02: '{target} = {source1} - {source2}', # Supported by OpenTTD Supported by TTDPatch Subtraction target = source1 - source2
|
||||
0x03: '{target} = {source1} * {source2} (Unsigned)', # Supported by OpenTTD Supported by TTDPatch Unsigned multiplication target = source1 * source2, with both sources being considered to be unsigned
|
||||
0x04: '{target} = {source1} * {source2} (Signed)', # Supported by OpenTTD Supported by TTDPatch Signed multiplication target = source1 * source2, with both sources considered signed
|
||||
0x05: '{target} = {source1} <</>> {source2} (Unsigned)', # Supported by OpenTTD Supported by TTDPatch Unsigned bit shift target = source1 << source2 if source2>0, or target = source1 >> abs(source2) if source2 < 0. source1 is considered to be unsigned
|
||||
0x06: '{target} = {source1} <</>> {source2} (Signed)', # Supported by OpenTTD Supported by TTDPatch Signed bit shift same as 05, but source1 is considered signed)
|
||||
0x07: '{target} = {source1} & {source2}', # Supported by OpenTTD Supported by TTDPatch 2.5 (alpha 48)2.5 Bitwise AND target = source1 AND source2
|
||||
0x08: '{target} = {source1} | {source2}', # Suported by OpenTTD Supported by TTDPatch 2.5 (alpha 48)2.5 Bitwise OR target = source1 OR source2
|
||||
0x09: '{target} = {source1} / {source2} (Unsigned)', # Supported by OpenTTD Supported by TTDPatch 2.5 (alpha 59)2.5 Unsigned division target = source1 / source2
|
||||
0x0A: '{target} = {source1} / {source2} (Signed)', # Supported by OpenTTD Supported by TTDPatch 2.5 (alpha 59)2.5 Signed division target = source1 / source2
|
||||
0x0B: '{target} = {source1} % {source2} (Unsigned)', # Supported by OpenTTD Supported by TTDPatch 2.5 (alpha 59)2.5 Unsigned modulo target = source1 % source2
|
||||
0x0C: '{target} = {source1} % {source2} (Signed)', # Supported by OpenTTD Supported by TTDPatch 2.5 (alpha 59)2.5 Signed modulo target = source1 % source2
|
||||
}
|
||||
|
||||
|
||||
def decode_actionD(data):
|
||||
target = data[0]
|
||||
operation = data[1]
|
||||
source1 = data[2]
|
||||
source2 = data[3]
|
||||
if source1 == 0xff or source2 == 0xff:
|
||||
value, _ = read_dword(data, 4)
|
||||
fmt = OPERATIONS[operation]
|
||||
sf = lambda x: f'[{x:02x}]' if x != 0xff else str(value)
|
||||
target_str = f'[{target:02x}]'
|
||||
op_str = fmt.format(target=target_str, source1=sf(source1), source2=sf(source2))
|
||||
print(f' <A>OP {op_str}')
|
||||
|
||||
|
||||
def decode_action14(data):
|
||||
res = {}
|
||||
ofs = 0
|
||||
|
||||
def decode_chunk(res):
|
||||
nonlocal ofs
|
||||
chunk_type = data[ofs]
|
||||
ofs += 1
|
||||
if chunk_type == 0: return False
|
||||
chunk_id = data[ofs: ofs + 4]
|
||||
ofs += 4
|
||||
if chunk_type == b'C'[0]:
|
||||
res[chunk_id] = {}
|
||||
while decode_chunk(res[chunk_id]):
|
||||
pass
|
||||
elif chunk_type == b'B'[0]:
|
||||
l = data[ofs] | (data[ofs + 1] << 8)
|
||||
res[chunk_id] = data[ofs + 2: ofs + 2 + l]
|
||||
ofs += 2 + l
|
||||
# elif chunk_type == b'T'[0]:
|
||||
else:
|
||||
assert False, chunk_type
|
||||
return True
|
||||
|
||||
while decode_chunk(res):
|
||||
pass
|
||||
|
||||
print(f' <14>INFO {res}')
|
||||
|
||||
|
||||
ACTIONS = {
|
||||
0x00: decode_action0,
|
||||
0x01: decode_action1,
|
||||
0x02: decode_action2,
|
||||
0x04: decode_action4,
|
||||
0x05: decode_action5,
|
||||
0x06: decode_action6,
|
||||
0x0a: decode_actionA,
|
||||
0x0d: decode_actionD,
|
||||
0x14: decode_action14,
|
||||
}
|
||||
|
||||
def read_pseudo_sprite(f):
|
||||
l = struct.unpack('<I', f.read(4))[0]
|
||||
if l == 0:
|
||||
print('End of pseudo sprites')
|
||||
return False
|
||||
grf_type = f.read(1)[0]
|
||||
grf_type_str = hex(grf_type)[2:]
|
||||
data = f.read(l)
|
||||
print(f'Sprite({l}, {grf_type_str}): ', hex_str(data[:100]))
|
||||
if grf_type == 0xff:
|
||||
decoder = ACTIONS.get(data[0])
|
||||
if decoder:
|
||||
decoder(data[1:])
|
||||
return True
|
||||
|
||||
def decode_sprite(f, num):
|
||||
data = b''
|
||||
while num > 0:
|
||||
code = f.read(1)[0]
|
||||
if code >= 128: code -= 256
|
||||
# print(f'Code {code} num {num}')
|
||||
if code >= 0:
|
||||
size = 0x80 if code == 0 else code
|
||||
num -= size
|
||||
if num < 0: raise RuntimeError('Corrupt sprite')
|
||||
data += f.read(size)
|
||||
else:
|
||||
data_offset = ((code & 7) << 8) | f.read(1)[0]
|
||||
#if (dest - data_offset < dest_orig.get()) return WarnCorruptSprite(file, file_pos, __LINE__);
|
||||
size = -(code >> 3)
|
||||
num -= size
|
||||
if num < 0: raise RuntimeError('Corrupt sprite')
|
||||
data += data[-data_offset:size - data_offset]
|
||||
if num != 0: raise RuntimeError('Corrupt sprite')
|
||||
return data
|
||||
|
||||
def read_real_sprite(f):
|
||||
sprite_id = struct.unpack('<I', f.read(4))[0]
|
||||
if sprite_id == 0:
|
||||
print(f'End of real sprites')
|
||||
return False
|
||||
print(f'Real sprite({sprite_id}): ', end='')
|
||||
num, t = struct.unpack('<IB', f.read(5))
|
||||
start_pos = f.tell()
|
||||
print(f'({num}, {t:02x}): ', end='')
|
||||
if t == 0xff:
|
||||
print('non-real (skip)')
|
||||
f.seek(start_pos + num - 1, 0)
|
||||
return True
|
||||
zoom, height, width, x_offs, y_offs = struct.unpack('<BHHhh', f.read(9))
|
||||
bpp = 1 # TODO
|
||||
decomp_size = struct.unpack('<I', f.read(4))[0] if t & 0x08 else width * height * bpp
|
||||
print(f'{width}x{height} zoom={zoom} x_offs={x_offs} y_offs={y_offs} bpp={bpp} decomp_size={decomp_size}')
|
||||
# data = decode_sprite(f, decomp_size)
|
||||
# print('Data: ', hex_str(data[:40]))
|
||||
f.seek(start_pos + num - 1, 0)
|
||||
return True
|
||||
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
print('Header:', hex_str(f.read(10)))
|
||||
data_offest, compression = struct.unpack('<IB', f.read(5))
|
||||
header_offset = f.tell() - 1
|
||||
print(f'Offset: {data_offest} compresion: {compression}')
|
||||
while read_pseudo_sprite(f):
|
||||
pass
|
||||
real_data_offset = f.tell() - header_offset
|
||||
# while read_real_sprite(f):
|
||||
# pass
|
||||
if data_offest != real_data_offset:
|
||||
print(f'[ERROR] Data offset check failed: {data_offest} {real_data_offset}')
|
||||
|
||||