def cargo_list(value, max_num_cargos): """ Encode an array of cargo types in a single property value. If less than the maximum number of cargos are given the rest is filled up with 0xFF (=invalid cargo). @param value: Array of cargo types. @type value: C{Array} @param max_num_cargos: The maximum number of cargos in the array. @type max_num_cargos: C{int} @param prop_num: Property number. @type prop_num: C{int} @param prop_size: Property size in bytes. @type prop_size: C{int} """ if not isinstance(value, Array) or len(value.values) > max_num_cargos: raise generic.ScriptError("Cargo list must be an array with no more than {:d} values".format(max_num_cargos), value.pos) cargoes = value.values + [ConstantNumeric(0xFF, value.pos) for _ in range(max_num_cargos - len(value.values))] ret = None for i, cargo in enumerate(cargoes): byte = BinOp(nmlop.AND, cargo, ConstantNumeric(0xFF, cargo.pos), cargo.pos) if i == 0: ret = byte else: byte = BinOp(nmlop.SHIFT_LEFT, byte, ConstantNumeric(i * 8, cargo.pos), cargo.pos) ret = BinOp(nmlop.OR, ret, byte, cargo.pos) return ret.reduce()
def mt_house_prop09(value, num_ids, size_bit): # Only bit 5 should be set for additional tiles # Additionally, correctly set the size bit (0, 2, 3 or 4) for the first tile if isinstance(value, ConstantNumeric) and (value.value & 0x1D) != 0: raise generic.ScriptError("Invalid bits set in house property 'building_flags'.", value.pos) ret = [BinOp(nmlop.OR, value, ConstantNumeric(1 << size_bit, value.pos), value.pos).reduce()] for _i in range(1, num_ids): ret.append(BinOp(nmlop.AND, value, ConstantNumeric(1 << 5, value.pos), value.pos).reduce()) return ret
def roadveh_speed_prop(prop_info): # prop 08 value is min(value, 255) prop08_value = lambda value: BinOp(nmlop.MIN, value, ConstantNumeric(0xFF, value.pos), value.pos).reduce() # prop 15 value is (value + 3) / 4 prop15_value = lambda value: BinOp(nmlop.DIV, BinOp(nmlop.ADD, value, ConstantNumeric(3, value.pos), value.pos), ConstantNumeric(4, value.pos), value.pos).reduce() # prop 15 should not be set if value(prop08_value) <= 255. But as we test prop15 and prop15 = 0.25/prop08, test for 64: prop15_test = lambda value: isinstance(value, ConstantNumeric) and value.value >= 0x40 prop08 = {'size': 1, 'num': 0x08, 'value_function': prop08_value} prop15 = {'size': 1, 'num': 0x15, 'value_function': prop15_value, 'test_function': prop15_test} for key in prop_info: prop08[key] = prop15[key] = prop_info[key] return [prop08, prop15]
def industry_input_multiplier(value, prop_num): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Input multiplier must be an array of up to two values", value.pos) val1 = value.values[0].reduce() if len(value.values) > 0 else ConstantNumeric(0) val2 = value.values[1].reduce() if len(value.values) > 1 else ConstantNumeric(0) if not isinstance(val1, (ConstantNumeric, ConstantFloat)) or not isinstance(val2, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Expected a compile-time constant", value.pos) generic.check_range(val1.value, 0, 256, "input_multiplier", val1.pos) generic.check_range(val2.value, 0, 256, "input_multiplier", val2.pos) mul1 = int(val1.value * 256) mul2 = int(val2.value * 256) return [Action0Property(prop_num, ConstantNumeric(mul1 | (mul2 << 16)), 4)]
def house_available_mask(value): # User sets [town_zones, climates] array # Which is mapped to (town_zones | (climates & 0x800) | ((climates & 0xF) << 12)) if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("availability_mask must be an array with exactly 2 values", value.pos) climates = BinOp(nmlop.AND, value.values[1], ConstantNumeric(0xF, value.pos), value.pos) climates = BinOp(nmlop.SHIFT_LEFT, climates, ConstantNumeric(12, value.pos), value.pos) above_snow = BinOp(nmlop.AND, value.values[1], ConstantNumeric(0x800, value.pos), value.pos) ret = BinOp(nmlop.OR, climates, value.values[0], value.pos) ret = BinOp(nmlop.OR, ret, above_snow, value.pos) return ret.reduce()
def animation_info(value, loop_bit=8, max_frame=253): """ Convert animation info array of two elements to an animation info property. The first is 0/1, and defines whether or not the animation loops. The second is the number of frames, at most 253 frames. @param value: Array of animation info. @type value: C{Array} @param loop_bit: Bit the loop information is stored. @type loop_bit: C{int} @param max_frame: Max frames possible. @type max_frame: C{int} @return: Value to use for animation property. @rtype: L{Expression} """ if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("animation_info must be an array with exactly 2 constant values", value.pos) looping = value.values[0].reduce_constant().value frames = value.values[1].reduce_constant().value if looping not in (0, 1): raise generic.ScriptError("First field of the animation_info array must be either 0 or 1", value.values[0].pos) if frames < 1 or frames > max_frame: raise generic.ScriptError("Second field of the animation_info array must be between 1 and " + str(max_frame), value.values[1].pos) return ConstantNumeric((looping << loop_bit) + frames - 1)
def house_random_colours(value): # User sets array with 4 values (range 0..15) # Output is a dword, each byte being a value from the array if not isinstance(value, Array) or len(value.values) != 4: raise generic.ScriptError("Random colours must be an array with exactly four values", value.pos) ret = None for i, colour in enumerate(value.values): if isinstance(colour, ConstantNumeric): generic.check_range(colour.value, 0, 15, "Random house colours", colour.pos) byte = BinOp(nmlop.AND, colour, ConstantNumeric(0xFF, colour.pos), colour.pos) if i == 0: ret = byte else: byte = BinOp(nmlop.SHIFT_LEFT, byte, ConstantNumeric(i * 8, colour.pos), colour.pos) ret = BinOp(nmlop.OR, ret, byte, colour.pos) return ret.reduce()
def object_size(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Object size must be an array with exactly two values", value.pos) sizex = value.values[0].reduce_constant() sizey = value.values[1].reduce_constant() if sizex.value < 1 or sizex.value > 15 or sizey.value < 1 or sizey.value > 15: raise generic.ScriptError("The size of an object must be at least 1x1 and at most 15x15 tiles", value.pos) return [Action0Property(0x0C, ConstantNumeric(sizey.value << 4 | sizex.value), 1)]
def industrytile_cargos(value): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) prop_num = 0x0A props = [] for cargo_amount_pair in value.values: if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) cargo_id = cargo_amount_pair.values[0].reduce_constant().value cargo_amount = cargo_amount_pair.values[1].reduce_constant().value props.append(Action0Property(prop_num, ConstantNumeric((cargo_amount << 8) | cargo_id), 2)) prop_num += 1 while prop_num <= 0x0C: props.append(Action0Property(prop_num, ConstantNumeric(0), 2)) prop_num += 1 return props
def industry_prod_multiplier(value): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Prod multiplier must be an array of up to two values", value.pos) props = [] for i in range(0, 2): val = value.values[i].reduce_constant() if i < len(value.values) else ConstantNumeric(0) props.append(Action0Property(0x12 + i, val, 1)) return props
def mt_house_old_id(value, num_ids, size_bit): # For substitute / override properties # Set value for tile i (0 .. 3) to (value + i) # Also validate that the size of the old house matches if isinstance(value, ConstantNumeric) and not value.value in old_houses[size_bit]: raise generic.ScriptError("Substitute / override house type must have the same size as the newly defined house.", value.pos) ret = [value] for i in range(1, num_ids): ret.append(BinOp(nmlop.ADD, value, ConstantNumeric(i, value.pos), value.pos).reduce()) return ret
def house_acceptance(value, index): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) if index < len(value.values): cargo_amount_pair = value.values[index] if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) return cargo_amount_pair.values[1] else: return ConstantNumeric(0, value.pos)
def industry_conflicting_types(value): if not isinstance(value, Array): raise generic.ScriptError("conflicting_ind_types must be an array of industry types", value.pos) if len(value.values) > 3: raise generic.ScriptError("conflicting_ind_types may have at most three entries", value.pos) types_list = [] for val in value.values: types_list.append(val.reduce_constant()) while len(types_list) < 3: types_list.append(ConstantNumeric(0xFF)) return [ConflictingTypesProp(types_list)]
def house_accepted_cargo_types(value): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) cargoes = [] for i in range(3): if i < len(value.values): cargo_amount_pair = value.values[i] if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) cargoes.append(cargo_amount_pair.values[0]) else: cargoes.append(ConstantNumeric(0xFF, value.pos)) ret = None for i, cargo in enumerate(cargoes): byte = BinOp(nmlop.AND, cargo, ConstantNumeric(0xFF, cargo.pos), cargo.pos) if i == 0: ret = byte else: byte = BinOp(nmlop.SHIFT_LEFT, byte, ConstantNumeric(i * 8, cargo.pos), cargo.pos) ret = BinOp(nmlop.OR, ret, byte, cargo.pos) return ret.reduce()
def house_prop_0A(value): # User sets an array [min_year, max_year] as property value # House property 0A is set to ((max_year - 1920) << 8) | (min_year - 1920) # With both bytes clamped to the 0 .. 255 range if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = BinOp(nmlop.SUB, value.values[0], ConstantNumeric(1920, value.pos), value.pos) min_year = BinOp(nmlop.MAX, min_year, ConstantNumeric(0, value.pos), value.pos) min_year = BinOp(nmlop.MIN, min_year, ConstantNumeric(255, value.pos), value.pos) max_year = BinOp(nmlop.SUB, value.values[1], ConstantNumeric(1920, value.pos), value.pos) max_year = BinOp(nmlop.MAX, max_year, ConstantNumeric(0, value.pos), value.pos) max_year = BinOp(nmlop.MIN, max_year, ConstantNumeric(255, value.pos), value.pos) max_year = BinOp(nmlop.SHIFT_LEFT, max_year, ConstantNumeric(8, value.pos), value.pos) return BinOp(nmlop.OR, max_year, min_year, value.pos).reduce()
def vehicle_length(value): if isinstance(value, ConstantNumeric): generic.check_range(value.value, 1, 8, "vehicle length", value.pos) return BinOp(nmlop.SUB, ConstantNumeric(8, value.pos), value, value.pos).reduce()
def mt_house_class(value, num_ids, size_bit): # Set class to 0xFF for additional tiles return [value] + (num_ids - 1) * [ConstantNumeric(0xFF, value.pos)]
def mt_house_zero(value, num_ids, size_bit): return [value] + (num_ids - 1) * [ConstantNumeric(0, value.pos)]
def zero_refit_mask(prop_num): # Zero the refit mask, in addition to setting some other refit property return {'size': 4, 'num': prop_num, 'value_function': lambda value: ConstantNumeric(0)}
def speed_fraction(value): # Unit is already converted to 0 .. 255 range when we get here if isinstance(value, ConstantNumeric) and not (0 <= value.value <= 255): # Do not use check_range to provide better error message raise generic.ScriptError("speed fraction must be in range 0 .. 1", value.pos) return BinOp(nmlop.SUB, ConstantNumeric(255, value.pos), value, value.pos).reduce()
def aircraft_is_heli(value): if isinstance(value, ConstantNumeric) and not value.value in (0, 2, 3): raise generic.ScriptError("Invalid value for aircraft_type", value.pos) return BinOp(nmlop.AND, value, ConstantNumeric(2, value.pos), value.pos).reduce()
def airport_years(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = value.values[0].reduce_constant() max_year = value.values[1].reduce_constant() return [Action0Property(0x0C, ConstantNumeric(max_year.value << 16 | min_year.value), 4)]
def mt_house_mask(mask, value, num_ids, size_bit): # Mask out the bits not present in the 'mask' parameter for additional tiles ret = [value] for _i in range(1, num_ids): ret.append(BinOp(nmlop.AND, value, ConstantNumeric(mask, value.pos), value.pos).reduce()) return ret
def two_byte_property(low_prop, high_prop, low_prop_info = {}, high_prop_info = {}): """ Decode a two byte value into two action 0 properties. @param low_prop: Property number for the low 8 bits of the value. @type low_prop: C{int} @param high_prop: Property number for the high 8 bits of the value. @type high_prop: C{int} @param low_prop_info: Dictionary with additional property information for the low byte. @type low_prop_info: C{dict} @param high_prop_info: Dictionary with additional property information for the low byte. @type high_prop_info: C{dict} @return: Sequence of two dictionaries with property information (low part, high part). @rtype: C{list} of C{dict} """ low_byte_info = {'num': low_prop, 'size': 1, 'value_function': lambda value: BinOp(nmlop.AND, value, ConstantNumeric(0xFF, value.pos), value.pos).reduce()} high_byte_info = {'num': high_prop, 'size': 1, 'value_function': lambda value: BinOp(nmlop.SHIFT_RIGHT, value, ConstantNumeric(8, value.pos), value.pos).reduce()} low_byte_info.update(low_prop_info) high_byte_info.update(high_prop_info) return [low_byte_info, high_byte_info]
def aircraft_is_large(value): return BinOp(nmlop.AND, value, ConstantNumeric(1, value.pos), value.pos).reduce()