alpine: Actions 1-2-3 for objects
This commit is contained in:
@@ -176,7 +176,7 @@ spriteset (meadow_transitions, "gfx/meadow_transitions.png") {
|
||||
tmpl_groundsprites(1, 14 * 64 + 1)
|
||||
tmpl_groundsprites(1, 15 * 64 + 1)
|
||||
}
|
||||
|
||||
/*
|
||||
spritelayout meadow_groundsprites_default {
|
||||
ground {
|
||||
sprite: meadow_transitions(
|
||||
@@ -187,8 +187,15 @@ spritelayout meadow_groundsprites_default {
|
||||
+ (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_default(n) {
|
||||
ground {
|
||||
sprite: meadow_groundsprites(n);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
spritelayout meadow_groundsprites_purchase {
|
||||
ground {
|
||||
sprite: meadow_groundsprites;
|
||||
@@ -196,12 +203,35 @@ spritelayout meadow_groundsprites_purchase {
|
||||
}
|
||||
|
||||
|
||||
switch (FEAT_OBJECTS, SELF, switch_meadow_groundsprites_default, [
|
||||
/*switch (FEAT_OBJECTS, SELF, switch_meadow_groundsprites_default, [
|
||||
STORE_TEMP(slope_to_sprite_offset(tile_slope), 0)
|
||||
]) {
|
||||
meadow_groundsprites_default;
|
||||
}
|
||||
*/
|
||||
|
||||
switch(FEAT_OBJECTS, SELF, meadow_ground_switch, tile_slope) {
|
||||
0: return meadow_groundsprites_default(0);
|
||||
1: return meadow_groundsprites_default(1);
|
||||
2: return meadow_groundsprites_default(2);
|
||||
4: return meadow_groundsprites_default(4);
|
||||
8: return meadow_groundsprites_default(8);
|
||||
9: return meadow_groundsprites_default(9);
|
||||
3: return meadow_groundsprites_default(3);
|
||||
6: return meadow_groundsprites_default(6);
|
||||
12: return meadow_groundsprites_default(12);
|
||||
5: return meadow_groundsprites_default(5);
|
||||
10: return meadow_groundsprites_default(10);
|
||||
11: return meadow_groundsprites_default(11);
|
||||
7: return meadow_groundsprites_default(7);
|
||||
14: return meadow_groundsprites_default(14);
|
||||
13: return meadow_groundsprites_default(13);
|
||||
27: return meadow_groundsprites_default(17);
|
||||
23: return meadow_groundsprites_default(16);
|
||||
30: return meadow_groundsprites_default(18);
|
||||
29: return meadow_groundsprites_default(15);
|
||||
default: return meadow_groundsprites_default(17);
|
||||
}
|
||||
|
||||
item (FEAT_OBJECTS, meadow) {
|
||||
property {
|
||||
@@ -214,12 +244,13 @@ item (FEAT_OBJECTS, meadow) {
|
||||
size: [1,1];
|
||||
}
|
||||
graphics {
|
||||
default: meadow_groundsprites_default;
|
||||
purchase: meadow_groundsprites_purchase;
|
||||
tile_check: CB_RESULT_LOCATION_ALLOW;
|
||||
/// default: meadow_groundsprites_default(0);
|
||||
default: meadow_ground_switch();
|
||||
//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)
|
||||
@@ -1705,3 +1736,4 @@ item (FEAT_OBJECTS, rivers_80) {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -8,29 +8,33 @@ gen = grf.NewGRF(
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
tmpl_ground_sprites(sprite, x, y, **kw)
|
||||
|
||||
|
||||
def replace_shore_sprites(sprite_id, file, x, y, **kw):
|
||||
@@ -105,6 +109,25 @@ def replace_coastal_sprites(file, x, y, **kw):
|
||||
replace_coastal_sprites('gfx/water/seashore_grid_temperate.gimp.png', 1, 1)
|
||||
|
||||
|
||||
# spriteset (meadow_groundsprites, "gfx/meadow_grid_temperate.png") { tmpl_groundsprites(1, 1) }
|
||||
|
||||
png = grf.ImageFile("gfx/meadow_grid_temperate.png")
|
||||
gen.add_sprite(grf.SpriteSet(grf.OBJECT, 19))
|
||||
tmpl_ground_sprites(lambda *args, **kw: gen.add_sprite(grf.FileSprite(png, *args, **kw)), 1, 1)
|
||||
gen.add_sprite(grf.BasicSpriteLayout(grf.OBJECT, 255))
|
||||
|
||||
gen.add_sprite(grf.Object(0,
|
||||
label=b'FLMA',
|
||||
size=0x11,
|
||||
climate=0xf,
|
||||
eol_date=0,
|
||||
flags=grf.Object.Flags.HAS_NO_FOUNDATION | grf.Object.Flags.ALLOW_UNDER_BRIDGE,
|
||||
))
|
||||
|
||||
gen.add_sprite(grf.Action3(grf.OBJECT, [0], [[255, 255]], 255))
|
||||
|
||||
{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}
|
||||
|
||||
# CREEKS
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ WATER_COLORS = set(range(0xF5, 0xFF))
|
||||
ZOOM_4X, ZOOM_NORMAL, ZOOM_2X, ZOOM_8X, ZOOM_16X, ZOOM_32X = range(6)
|
||||
BPP_8, BPP_32 = range(2)
|
||||
|
||||
OBJECT = 0x0f
|
||||
|
||||
|
||||
def color_distance(c1, c2):
|
||||
rmean = (c1.rgb[0] + c2.rgb[0]) / 2.
|
||||
r = c1.rgb[0] - c2.rgb[0]
|
||||
@@ -43,6 +46,81 @@ def find_best_color(x, in_range=SAFE_COLORS):
|
||||
# assert im.mode == 'RGB', im.mode
|
||||
# data = np.array(im)
|
||||
|
||||
OP_ADD = 0x00
|
||||
OP_SUB = 0x01
|
||||
OP_TSTO = 0x0E
|
||||
OP_PSTO = 0x10
|
||||
|
||||
OPERATORS = {
|
||||
OP_ADD: ('{a} + {b}', 2),
|
||||
OP_SUB: ('{a} - {b}', 2),
|
||||
OP_TSTO: ('TEMP[{b}] = {a}', 1),
|
||||
OP_PSTO: ('PERM[{b}] = {a}', 1),
|
||||
}
|
||||
|
||||
DEFAULT_INDENT_STR = ' '
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _make_node(self, value):
|
||||
if isinstance(value, int):
|
||||
return Value(value)
|
||||
assert isinstance(value, Node)
|
||||
return value
|
||||
|
||||
def __add__(self, other):
|
||||
return Expr(OP_ADD, self, self._make_node(other))
|
||||
|
||||
def __sub__(self, other):
|
||||
return Expr(OP_SUB, self, self._make_node(other))
|
||||
|
||||
def store_temp(self, register):
|
||||
return Expr(OP_TSTO, self, self._make_node(register))
|
||||
|
||||
def store_perm(self, register):
|
||||
return Expr(OP_PSTO, self, self._make_node(register))
|
||||
|
||||
def format(self, parent_priority=0, indent=0, indent_str=DEFAULT_INDENT_STR):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
return self.format()
|
||||
|
||||
def __repr__(self):
|
||||
return self.format()
|
||||
|
||||
|
||||
class Expr(Node):
|
||||
def __init__(self, op, a, b):
|
||||
self.op = op
|
||||
self.a = a
|
||||
self.b = b
|
||||
|
||||
def format(self, parent_priority=0, indent=0, indent_str=DEFAULT_INDENT_STR):
|
||||
if self.op in OPERATORS:
|
||||
fmt, prio = OPERATORS[self.op]
|
||||
else:
|
||||
fmt, prio = '{a} <{self.op:02x}> {b}', 1
|
||||
astr = self.a.format(prio, indent=0)
|
||||
bstr = self.b.format(prio, indent=0)
|
||||
res = fmt.format(a=astr, b=bstr)
|
||||
suffix = indent_str * indent
|
||||
if prio <= parent_priority:
|
||||
return suffix + f'({res})'
|
||||
return suffix + res
|
||||
|
||||
|
||||
class Value(Node):
|
||||
def __init__(self, value):
|
||||
super().__init__()
|
||||
self.value = value
|
||||
|
||||
def format(self, parent_priority=0, indent=0, indent_str=DEFAULT_INDENT_STR):
|
||||
return indent_str * indent + str(self.value)
|
||||
|
||||
|
||||
class BaseSprite:
|
||||
def get_data(self):
|
||||
@@ -253,7 +331,7 @@ class InformationSprite(BaseSprite): # action 14
|
||||
return len(self._data)
|
||||
|
||||
|
||||
class ReplaceSprites: # action A
|
||||
class ReplaceSprites(BaseSprite): # action A
|
||||
def __init__(self, sets):
|
||||
assert isinstance(sets, (list, tuple))
|
||||
assert len(sets) <= 0xff
|
||||
@@ -270,7 +348,7 @@ class ReplaceSprites: # action A
|
||||
return 2 + 3 * len(self.sets)
|
||||
|
||||
|
||||
class ReplaceNewSprites: # action 5
|
||||
class ReplaceNewSprites(BaseSprite): # action 5
|
||||
def __init__(self, set_type, num): # TODO offset
|
||||
assert isinstance(set_type, int)
|
||||
assert isinstance(num, int)
|
||||
@@ -283,6 +361,159 @@ class ReplaceNewSprites: # action 5
|
||||
def get_data_size(self):
|
||||
return 5
|
||||
|
||||
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_OBJECT_PROP_DICT = {name: (id, size) for id, name, size in ACTION0_OBJECT_PROPS}
|
||||
|
||||
|
||||
class LazyBaseSprite(BaseSprite):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._data = None
|
||||
|
||||
def _encode(self):
|
||||
raise NotImplemented
|
||||
|
||||
def get_data(self):
|
||||
if self._data is None:
|
||||
self._data = self._encode()
|
||||
return self._data
|
||||
|
||||
def get_data_size(self):
|
||||
return len(self.get_data())
|
||||
|
||||
class Action0(LazyBaseSprite): # action 0
|
||||
def __init__(self, feature, first_id, count, props):
|
||||
super().__init__()
|
||||
self.feature = feature
|
||||
self.first_id = first_id
|
||||
self.count = count
|
||||
if feature != OBJECT:
|
||||
raise NotImplemented
|
||||
self.props = props
|
||||
assert all(x in ACTION0_OBJECT_PROP_DICT for x in props)
|
||||
|
||||
def _encode_value(self, value, fmt):
|
||||
if fmt == 'B': return struct.pack('<B', value)
|
||||
if fmt == 'W': return struct.pack('<H', value)
|
||||
if fmt == 'D': return struct.pack('<I', value)
|
||||
if fmt == 'L':
|
||||
if isinstance(value, int): return struct.pack('<I', value)
|
||||
assert isinstance(value, bytes)
|
||||
assert len(value) == 4, len(value)
|
||||
return value
|
||||
if fmt == 'B*': return struct.pack('<BH', 255, value)
|
||||
if fmt == 'n*B':
|
||||
assert isinstance(value, bytes)
|
||||
assert len(value) < 256, len(value)
|
||||
return struct.pack('<B', len(value)) + value
|
||||
|
||||
def _encode(self):
|
||||
res = struct.pack('<BBBBBH',
|
||||
0, self.feature, len(self.props), self.count, 255, self.first_id)
|
||||
for prop, value in self.props.items():
|
||||
code, fmt = ACTION0_OBJECT_PROP_DICT[prop]
|
||||
res += bytes((code,)) + self._encode_value(value, fmt)
|
||||
return res
|
||||
|
||||
|
||||
class Object(Action0):
|
||||
class Flags:
|
||||
NONE = 0 # Just nothing.
|
||||
ONLY_IN_SCENEDIT = 1 << 0 # Object can only be constructed in the scenario editor.
|
||||
CANNOT_REMOVE = 1 << 1 # Object can not be removed.
|
||||
AUTOREMOVE = 1 << 2 # Object get automatically removed (like "owned land").
|
||||
BUILT_ON_WATER = 1 << 3 # Object can be built on water (not required).
|
||||
CLEAR_INCOME = 1 << 4 # When object is cleared a positive income is generated instead of a cost.
|
||||
HAS_NO_FOUNDATION = 1 << 5 # Do not display foundations when on a slope.
|
||||
ANIMATION = 1 << 6 # Object has animated tiles.
|
||||
ONLY_IN_GAME = 1 << 7 # Object can only be built in game.
|
||||
CC2_COLOUR = 1 << 8 # Object wants 2CC colour mapping.
|
||||
NOT_ON_LAND = 1 << 9 # Object can not be on land, implicitly sets #OBJECT_FLAG_BUILT_ON_WATER.
|
||||
DRAW_WATER = 1 << 10 # Object wants to be drawn on water.
|
||||
ALLOW_UNDER_BRIDGE = 1 << 11 # Object can built under a bridge.
|
||||
ANIM_RANDOM_BITS = 1 << 12 # Object wants random bits in "next animation frame" callback.
|
||||
SCALE_BY_WATER = 1 << 13 # Object count is roughly scaled by water amount at edges.
|
||||
|
||||
|
||||
def __init__(self, id, **props):
|
||||
super().__init__(OBJECT, id, 1, props)
|
||||
|
||||
|
||||
class Action1(LazyBaseSprite):
|
||||
def __init__(self, feature, set_count, sprite_count):
|
||||
super().__init__()
|
||||
self.feature = feature
|
||||
self.set_count = set_count
|
||||
self.sprite_count = sprite_count
|
||||
|
||||
def _encode(self):
|
||||
return struct.pack('<BBBBH', 0x01,
|
||||
self.feature, self.set_count, 0xFF, self.sprite_count)
|
||||
|
||||
|
||||
class SpriteSet(Action1):
|
||||
def __init__(self, feature, sprite_count):
|
||||
super().__init__(feature, 1, sprite_count)
|
||||
|
||||
|
||||
class BasicSpriteLayout(LazyBaseSprite):
|
||||
def __init__(self, feature, ref_id, xofs=0, yofs=0, extent=(0, 0, 0)):
|
||||
super().__init__()
|
||||
assert feature in (0x07, 0x09, OBJECT, 0x11), feature
|
||||
self.feature = feature
|
||||
self.ref_id = ref_id
|
||||
self.xofs = xofs
|
||||
self.yofs = yofs
|
||||
self.extent = extent
|
||||
|
||||
def _encode_sprite(self, id=0, mode=0, recolor=0, draw_in_transparent=False, use_last=False):
|
||||
return id | (mode << 14) | (recolor << 16) | (draw_in_transparent << 30) | (use_last << 31)
|
||||
|
||||
def _encode(self):
|
||||
return struct.pack('<BBBBIIbbBBB', 0x02, self.feature, self.ref_id, 0,
|
||||
# self._encode_sprite(), self._encode_sprite(),
|
||||
self._encode_sprite(use_last=True), 0,
|
||||
self.xofs, self.yofs,
|
||||
*self.extent)
|
||||
|
||||
|
||||
class Action3(LazyBaseSprite):
|
||||
def __init__(self, feature, ids, maps, default):
|
||||
super().__init__()
|
||||
self.feature = feature
|
||||
self.ids = ids
|
||||
self.maps = maps
|
||||
self.default = default
|
||||
|
||||
def _encode(self):
|
||||
idcount = len(self.ids)
|
||||
mcount = len(self.maps)
|
||||
res = struct.pack(
|
||||
'<BBB' + 'B' * idcount + 'B' + 'BH' * mcount + 'H',
|
||||
0x03, self.feature, idcount,
|
||||
*self.ids, mcount, *sum(self.maps, []),
|
||||
self.default)
|
||||
return res
|
||||
|
||||
|
||||
class NewGRF:
|
||||
def __init__(self, grfid, name, description):
|
||||
|
||||
@@ -2,6 +2,8 @@ import sys
|
||||
import struct
|
||||
from nml import lz77
|
||||
|
||||
from grf import Node, Expr, Value
|
||||
|
||||
|
||||
def hex_str(s):
|
||||
if isinstance(s, (bytes, memoryview)):
|
||||
@@ -145,7 +147,6 @@ def read_property(data, ofs, fmt):
|
||||
|
||||
|
||||
def decode_action0(data):
|
||||
num = data[0]
|
||||
feature = data[0]
|
||||
num_props = data[1]
|
||||
num_info = data[2]
|
||||
@@ -164,7 +165,6 @@ def decode_action0(data):
|
||||
|
||||
|
||||
def decode_action1(data):
|
||||
num = data[0]
|
||||
feature = data[0]
|
||||
num_sets = data[1]
|
||||
num_ent, _ = read_extended_byte(data, 2)
|
||||
@@ -270,6 +270,33 @@ def read_sprite_layout_registers(d, flags, is_parent):
|
||||
return regs
|
||||
|
||||
|
||||
class Set:
|
||||
def __init__(self, set_id):
|
||||
self.is_callback = bool(set_id & 0x8000)
|
||||
self.set_id = set_id & 0x7fff
|
||||
|
||||
def __str__(self):
|
||||
if self.is_callback:
|
||||
return f'CB({self.set_id})'
|
||||
return f'Set({self.set_id})'
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class Range:
|
||||
def __init__(self, low, high, set):
|
||||
self.set = set
|
||||
self.low = low
|
||||
self.high = high
|
||||
|
||||
def __str__(self):
|
||||
if self.low == self.high:
|
||||
return f'{self.low} -> {self.set}'
|
||||
return f'{self.low}..{self.high} -> {self.set}'
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
def read_sprite_layout(d, num, no_z_position):
|
||||
has_z_position = not no_z_position
|
||||
has_flags = bool((num >> 6) & 1)
|
||||
@@ -302,6 +329,127 @@ def read_sprite_layout(d, num, no_z_position):
|
||||
'sprites': sprites
|
||||
}
|
||||
|
||||
|
||||
VA2_GLOBALS = {
|
||||
0x00: ('date', 'W'), # 80 W current date (counted as days from 1920)[1]
|
||||
0x01: ('year', 'B'), # 81 B 0.6 2.0 Current year (count from 1920, max. 2175 even with eternalgame)[1]
|
||||
0x02: ('month', 'B/D'), # 82 B/D 0.6 2.0 current month (0-11) in bits 0-7; the higher bytes contain unusable junk.[1] 0.7  Since OpenTTD r13594 'day of month' (0-30) is stored in bits 8-12, bit 15 is set in leapyears and 'day of year'(0-364 resp. 365) is stored in bits 16-24. All other bits are reserved and should be masked.
|
||||
0x03: ('climate', 'B'), # 83 B 0.6 2.0 Current climate: 00 = temp, 01 = arctic, 02 = trop, 03 = toyland
|
||||
# 84 D 0.6 2.0 GRF loading stage, see below
|
||||
# 85 B 0.6 2.0 TTDPatch flags: only for bit tests
|
||||
0x06: ('drive_side', 'B'), # 86 B 0.6 2.0 Road traffic side: bit 4 clear=left, set=right; other bits are reserved and must be masked. (87) (87) B   No longer used since TTDPatch 2.0. (was width of "€" character)
|
||||
# 88 4*B 0.6 2.0 Checks specified GRFID (see condition-types)[2]
|
||||
0x09: ('date_fract', 'W'), # 89 W 0.6 2.0 date fraction, incremented by 0x375 every engine tick
|
||||
0x0A: ('anim_counter', 'W'), # 8A W 0.6 2.0 animation counter, incremented every tick
|
||||
0x0B: ('ttdp_version', 'D'), # 8B D  2.0 TTDPatch version, see below [3][4]
|
||||
0x0C: ('cur_cb_id', 'W'), # W 0.6 2.5 current callback ID (feature-specific), set to 00 when not in a callback
|
||||
0x0D: ('ttd_version', 'B'), # 8D B 0.6 2.5 TTD version, 0=DOS, 1=Windows
|
||||
0x0E: ('train_y_ofs', 'B'), # 8E 8E B 0.6 2.5 Y-Offset for train sprites
|
||||
0x0F: ('rail_cost', '3*B'), # 8F 8F 3*B 0.6 2.5 Rail track type cost factors
|
||||
0x10: ('cb_info1', 'D'), # D 0.6 2.5 Extra callback info 1, see below.
|
||||
0x11: ('cur_rail_tool', 'B'), # B  2.5 current rail tool type (for station callbacks)
|
||||
0x12: ('game_mode', 'B'), # 92 B 0.6 2.5 Game mode, 0 in title screen, 1 in game and 2 in editor
|
||||
0x13: ('tile_refresh_left', 'W'), # 93 93 W  2.5 Tile refresh offset to left [5]
|
||||
0x14: ('tile_refresh_right', 'W'), # 94 94 W  2.5 Tile refresh offset to right [5]
|
||||
0x15: ('tile_refresh_up', 'W'), # 95 95 W  2.5 Tile refresh offset upwards [5]
|
||||
0x16: ('tile_refresh_down', 'W'), # 96 96 W  2.5 Tile refresh offset downwards [5]
|
||||
# 97 97 B  2.5 Fixed snow line height [6][7]
|
||||
0x18: ('cb_info2', 'D'), # D 0.6 2.5 Extra callback info 2, see below.
|
||||
# 99 99 D  2.5 Global ID offset
|
||||
0x1A: ('max_uint32', 'D'), # 9A D 0.6 2.5 Has always all bits set; you can use this to make unconditional jumps
|
||||
0x1B: ('display_options', 'B'), # B  2.5 display options; bit 0=town names, 1=station names, 2=signs, 3=animation, 4=transparency, 5=full detail
|
||||
0x1C: ('va2_ret', 'D'), # D 0.6 2.5 result from most recent VarAction2
|
||||
0x1D: ('ttd_platform', 'D'), # 9D D 0.6 2.5 TTD Platform, 0=TTDPatch, 1=OpenTTD [4]
|
||||
0x1E: ('grf_featuers', 'D'), # 9E 9E D 0.6 2.5 Misc. GRF Features
|
||||
# 9F D  2.5 writable only: Locale-dependent settings
|
||||
0x20: ('snow_line', 'B'), # B 0.6 2.5 Current snow line height, FFh if snow isn't present at all [7]
|
||||
0x21: ('openttd_version', 'D'), # A1 D 0.6  OpenTTD version, see below. [4]
|
||||
0x22: ('difficulty_level', 'D'), # A2 D 0.7 2.6 Difficulty level: 00= easy, 01=medium, 02=hard, 03=custom
|
||||
0x23: ('date_long', 'D'), # A3 D 0.7 2.6 Current date long format
|
||||
0x24: ('year_zero', 'D'), # A4 D 0.7 2.6 Current year zero based
|
||||
0x25: ('a3_grfid', 'D'), # D 0.7  GRFID of the grf that contains the corresponding Action3. Useful when accessing the "related" object. Currently only supported for vehicles.
|
||||
}
|
||||
|
||||
V2_OBJECT_VARS = {
|
||||
0x40: ('relative_pos', 'D'), # Relative position, like Industry Tile var43
|
||||
0x41: ('tile_info', 'W'), # Tile information, see below
|
||||
0x42: ('constructed', 'D'), # Construction date from year 0
|
||||
0x43: ('anim_counter', 'B'), # Animation counter, see below
|
||||
0x44: ('founder', 'B'), # Object founder information
|
||||
0x45: ('closest_town_info', 'D'), # Get town zone and Manhattan distance of closest town
|
||||
0x46: ('closest_town_dist_squared', 'D'), # Get square of Euclidian distance of closest town
|
||||
0x47: ('colour', 'B'), # Object colour
|
||||
0x48: ('views', 'B'), # Object views
|
||||
0x60: ('type_view_ofs', 'W'), # Get object type and view at offset
|
||||
0x61: ('random_ofs', 'B'), # Get random bits at offset
|
||||
0x62: ('nearby_tile_info', 'D'), # Land info of nearby tiles
|
||||
0x63: ('nearby_anim_counter', 'W'), # Animation counter of nearby tile
|
||||
0x64: ('object_count', 'D'), # Count of object, distance of closest instance
|
||||
}
|
||||
|
||||
VA2_OP = {
|
||||
0x00: '+', # \2+ result = val1 + val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5
|
||||
0x01: '-', # \2- result = val1 - val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5
|
||||
0x02: 'min', # \2< result = min(val1, val2) Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 val1 and val2 are both considered signed
|
||||
0x03: 'max', # \2> result = max(val1, val2) Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5
|
||||
0x04: 'min', # \2u< result = min(val1, val2) Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 val1 and val2 are both considered unsigned
|
||||
0x05: 'max', # \2u> result = max(val1, val2) Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5
|
||||
0x06: '/', # \2/ result = val1 / val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 val1 and val2 are both considered signed
|
||||
0x07: '%', # \2% result = val1 mod val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5
|
||||
0x08: 'u/', # \2u/ result = val1 / val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 val1 and val2 are both considered unsigned
|
||||
0x09: 'u%', # \2u% result = val1 mod val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5
|
||||
0x0A: '*', # \2* result = val1 * val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 result will be truncated to B/W/D (that makes it the same for signed/unsigned operands)
|
||||
0x0B: '&', # \2& result = val1 & val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 bitwise AND
|
||||
0x0C: '|', # \2| result = val1 | val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 bitwise OR
|
||||
0x0D: '^', # \2^ result = val1 ^ val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.52.5 bitwise XOR
|
||||
0x0E: '(tsto)', # \2s or \2sto [1] var7D[val2] = val1, result = val1 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.6 (r1246)2.6 Store result. See Temporary storage.
|
||||
0x0F: ';', # \2r or \2rst [1] result = val2 [2] Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.6 (r1246)2.6
|
||||
0x10: '(psto)', # \2psto [3] var7C[val2] = val1, result = val1 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.6 (r1315)2.6 Store result into persistent storage. See Persistent storage.
|
||||
0x11: '(ror)', # \2ror or \2rot [4] result = val1 rotate right val2 Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.6 (r1651)2.6 Always a 32-bit rotation.
|
||||
0x12: '(cmp)', # \2cmp [3] see notes Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.6 (r1698)2.6 Result is 0 if val1<val2, 1 if val1=val2 and 2 if val1>val2. Both values are considered signed. [5]
|
||||
0x13: '(ucmp)', # \2ucmp [3] see notes Supported by OpenTTD 0.60.6 Supported by TTDPatch 2.6 (r1698)2.6 The same as 12, but operands are considered unsigned. [5]
|
||||
0x14: '<<', # \2<< [3] result = val1 << val2 Supported by OpenTTD 1.1 (r20332)1.1 Supported by TTDPatch 2.6 (r2335)2.6 shift left; val2 should be in the range 0 to 31.
|
||||
0x15: 'u>>', # \2u>> [3] result = val1 >> val2 Supported by OpenTTD 1.1 (r20332)1.1 Supported by TTDPatch 2.6 (r2335)2.6 shift right (unsigned); val2 should be in the range 0 to 31.
|
||||
0x16: '>>', # \2>> [3] result = val1 >> val2 Supported by OpenTTD 1.1 (r20332)1.1 Supported by TTDPatch 2.6 (r2335)2.6 shift right (signed); val2 should be in the range 0 to 31.
|
||||
}
|
||||
|
||||
|
||||
def get_va2_var(var):
|
||||
if var < 0x40:
|
||||
name, fmt = VA2_GLOBALS[var]
|
||||
return f'[{name}]', fmt
|
||||
if var == 0x5f: return '(random)', 'D'
|
||||
if var == 0x7b: return '(var_eval)', ''
|
||||
if var == 0x7c: return '(perm)', 'D'
|
||||
if var == 0x7d: return '(temp)', 'D'
|
||||
if var == 0x7e: return '(call)', 'D'
|
||||
if var == 0x7f: return '(param)', 'D'
|
||||
return V2_OBJECT_VARS[var]
|
||||
|
||||
|
||||
class Call(Node):
|
||||
def __init__(self, subroutine):
|
||||
self.suroutine = subroutine
|
||||
|
||||
|
||||
class Generic(Node):
|
||||
def __init__(self, var, shift, and_mask, type, add_val, divmod_val):
|
||||
self.var = var
|
||||
self.shift = shift
|
||||
self.and_mask = and_mask
|
||||
self.type = type
|
||||
self.add_val = add_val
|
||||
self.divmod_val = divmod_val
|
||||
|
||||
def format(self, parent_priority=0, indent=0, indent_str=' '):
|
||||
addstr = ''
|
||||
if self.type == 1:
|
||||
addstr = ' +{self.add_val} /{self.divmod_val}'
|
||||
elif self.type == 2:
|
||||
addstr = ' +{self.add_val} %{self.divmod_val}'
|
||||
return (indent_str * indent) + f'(var: {self.var} >>{self.shift} &{self.and_mask}{addstr})'
|
||||
|
||||
|
||||
def decode_action2(data):
|
||||
feature = data[0]
|
||||
set_id = data[1]
|
||||
@@ -315,50 +463,78 @@ def decode_action2(data):
|
||||
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} '
|
||||
print(f'BASIC 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):
|
||||
# varact2
|
||||
group_size = (num_ent1 >> 2) & 3
|
||||
related_scope = bool(num_ent1 & 2)
|
||||
first = True
|
||||
ofs = 3
|
||||
adjusts = []
|
||||
root = None
|
||||
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
|
||||
op = 0 if first else d.get_byte()
|
||||
var = d.get_byte()
|
||||
if 0x60 <= var < 0x80:
|
||||
param = d.get_byte()
|
||||
varadj = d.get_byte()
|
||||
res['shift_num'] = varadj & 0x1f
|
||||
shift = 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)
|
||||
node_type = varadj >> 6
|
||||
and_mask = d.get_var(group_size)
|
||||
if node_type != 0:
|
||||
# old magic, use advaction2 instead
|
||||
add_val = d.get_var(group_size)
|
||||
divmod_val = d.get_var(group_size)
|
||||
node = Generic(var, shift, and_mask, node_type, add_val, divmod_val)
|
||||
elif var == 0x1a and shift == 0:
|
||||
node = Value(and_mask)
|
||||
else:
|
||||
node = Generic(var, shift, and_mask, 0, None, None)
|
||||
|
||||
if first:
|
||||
root = node
|
||||
else:
|
||||
root = Expr(op, root, node)
|
||||
|
||||
first = False
|
||||
if not has_more:
|
||||
break
|
||||
|
||||
# no ranges is special for "do not switch, return the switch value"
|
||||
# <frosch123> oh, also, the ranges are unsigned
|
||||
# <frosch123> so if you want to set -5..5 you have to split into two ranges -5..-1, 0..5
|
||||
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))
|
||||
ranges.append(Range(low, high, Set(group)))
|
||||
|
||||
default_group = d.get_word()
|
||||
|
||||
print(f'default_group: {default_group} adjusts:{adjusts} ranges:{ranges} ')
|
||||
default_group = Set(d.get_word())
|
||||
|
||||
print(f'VARACT default_group:{default_group} related_scope:{related_scope} ranges:{ranges} ')
|
||||
# for a in adjusts:
|
||||
# var = a['var']
|
||||
# name, fmt = get_va2_var(var)
|
||||
# op = VA2_OP[a['op']]
|
||||
# param_str = ''
|
||||
# if 0x60 <= var < 0x80:
|
||||
# if var == 0x7e:
|
||||
# param_str = ' proc:{:02x}'.format(a['subroutine'])
|
||||
# else:
|
||||
# param_str = ' param:{:02x}'.format(a['parameter'])
|
||||
# type_str = ''
|
||||
# if a['type'] != 0:
|
||||
# type_str = '+{add_val} /%{divmod_val}'.format(**a)
|
||||
# print(f' op<{a["op"]}>:{op} var<{var:02x}>:{name}({fmt}){param_str} type:{a["type"]} >>{a["shift_num"]} &{a["and_mask"]:x}{type_str}')
|
||||
print()
|
||||
print(root.format(indent=1))
|
||||
return
|
||||
|
||||
layout = read_sprite_layout(d, max(num_ent1, 1), num_ent1 == 0)
|
||||
@@ -378,6 +554,26 @@ def decode_action2(data):
|
||||
print(f'ent1:{ent1} ent2:{ent2}')
|
||||
|
||||
|
||||
def decode_action3(data):
|
||||
feature = data[0]
|
||||
idcount = data[1]
|
||||
print(f' <3>MAP feature:{str_feature(feature)} ', end='')
|
||||
if data[1] == 0:
|
||||
_, set_id = struct.unpack_from('<BH', data, offset=2)
|
||||
print(f'set_id:{set_id}');
|
||||
else:
|
||||
d = DataReader(data, 2)
|
||||
objs = [d.get_byte() for _ in range(idcount)]
|
||||
cidcount = d.get_byte()
|
||||
maps = []
|
||||
for _ in range(cidcount):
|
||||
ctype = d.get_byte()
|
||||
groupid = d.get_word()
|
||||
maps.append({'ctype': ctype, 'groupid': groupid})
|
||||
def_gid = d.get_word()
|
||||
print(f'objs:{objs} maps:{maps} default_gid:{def_gid}')
|
||||
|
||||
|
||||
def decode_action4(data):
|
||||
fmt = '<BBB' + ('H' if data[1] & 0xf0 else 'B')
|
||||
feature, lang, num, offset = struct.unpack_from(fmt, data)
|
||||
@@ -479,6 +675,7 @@ ACTIONS = {
|
||||
0x00: decode_action0,
|
||||
0x01: decode_action1,
|
||||
0x02: decode_action2,
|
||||
0x03: decode_action3,
|
||||
0x04: decode_action4,
|
||||
0x05: decode_action5,
|
||||
0x06: decode_action6,
|
||||
@@ -487,7 +684,7 @@ ACTIONS = {
|
||||
0x14: decode_action14,
|
||||
}
|
||||
|
||||
def read_pseudo_sprite(f):
|
||||
def read_pseudo_sprite(f, nfo_line):
|
||||
l = struct.unpack('<I', f.read(4))[0]
|
||||
if l == 0:
|
||||
print('End of pseudo sprites')
|
||||
@@ -495,7 +692,7 @@ def read_pseudo_sprite(f):
|
||||
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]))
|
||||
print(f'{nfo_line}: Sprite({l}, {grf_type_str}): ', hex_str(data[:100]))
|
||||
if grf_type == 0xff:
|
||||
decoder = ACTIONS.get(data[0])
|
||||
if decoder:
|
||||
@@ -550,8 +747,10 @@ with open(sys.argv[1], 'rb') as f:
|
||||
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
|
||||
print('Magic sprite:', hex_str(f.read(5 + 4)))
|
||||
nfo_line = 1
|
||||
while read_pseudo_sprite(f, nfo_line):
|
||||
nfo_line += 1
|
||||
real_data_offset = f.tell() - header_offset
|
||||
# while read_real_sprite(f):
|
||||
# pass
|
||||
|
||||
Reference in New Issue
Block a user