def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replacenew-block", pos) sprite_container.SpriteContainer.__init__(self, "replacenew-block", name) num_params = len(param_list) if not (1 <= num_params <= 3): raise generic.ScriptError( "replacenew-block requires 1 to 3 parameters, encountered " + str(num_params), pos) self.type = param_list[0] if not isinstance(self.type, expression.Identifier): raise generic.ScriptError( "replacenew parameter 'type' must be an identifier of a sprite replacement type", self.type.pos) if num_params >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "replacenew-block parameter 2 'file' must be a string literal", self.image_file.pos) else: self.image_file = None if num_params >= 3: self.offset = param_list[2].reduce_constant().value generic.check_range(self.offset, 0, 0xFFFF, "replacenew-block parameter 3 'offset'", param_list[2].pos) else: self.offset = 0 self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos)
def resolve_spritegroup_ref(self, sg_ref): """ Resolve a reference to a (sprite/palette) sprite group @param sg_ref: Reference to a sprite group @type sg_ref: L{SpriteGroupRef} @return: Sprite number (index of action1 set) to use @rtype: L{Expression} """ spriteset = action2.resolve_spritegroup(sg_ref.name) if len(sg_ref.param_list) == 0: offset = None elif len(sg_ref.param_list) == 1: id_dicts = [(spriteset.labels, lambda val, pos: expression.ConstantNumeric(val, pos))] offset = action2var.reduce_varaction2_expr(sg_ref.param_list[0], self.feature, self.extra_dicts + id_dicts) if isinstance(offset, expression.ConstantNumeric): generic.check_range(offset.value, 0, len(real_sprite.parse_sprite_data(spriteset)) - 1, "offset within spriteset", sg_ref.pos) else: raise generic.ScriptError("Expected 0 or 1 parameter, got " + str(len(sg_ref.param_list)), sg_ref.pos) num = action1.get_action1_index(spriteset) generic.check_range(num, 0, (1 << 14) - 1, "sprite", sg_ref.pos) return expression.ConstantNumeric(num), offset
def builtin_visual_effect_and_powered(name, args, pos): """ Builtin function, used in two forms: visual_effect_and_powered(effect, offset, powered) visual_effect(effect, offset) Use this to set the vehicle property visual_effect[_and_powered] and for the callback VEH_CB_VISUAL_EFFECT[_AND_POWERED] """ arg_len = 2 if name == "visual_effect" else 3 if len(args) != arg_len: raise generic.ScriptError(name + "() must have {:d} parameters".format(arg_len), pos) effect = args[0].reduce_constant(global_constants.const_list).value offset = nmlop.ADD(args[1], 8).reduce_constant().value generic.check_range(offset, 0, 0x0F, "offset in function " + name, pos) if arg_len == 3: powered = args[2].reduce_constant(global_constants.const_list).value if powered != 0 and powered != 0x80: raise generic.ScriptError( "3rd argument to visual_effect_and_powered (powered) must be" " either ENABLE_WAGON_POWER or DISABLE_WAGON_POWER", pos, ) else: powered = 0 return ConstantNumeric(effect | offset | powered)
def builtin_slope_to_sprite_offset(name, args, pos): """ builtin function slope_to_sprite_offset(slope) @return sprite offset to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '{}'".format(name), args[0].pos) # step 1: ((slope >= 0) & (slope <= 14)) * slope # This handles all non-steep slopes expr = BinOp(nmlop.AND, BinOp(nmlop.CMP_LE, args[0], ConstantNumeric(14), pos), BinOp(nmlop.CMP_GE, args[0], ConstantNumeric(0), pos), pos) expr = BinOp(nmlop.MUL, expr, args[0], pos) # Now handle the steep slopes separately # So add (slope == SLOPE_XX) * offset_of_SLOPE_XX for each steep slope steep_slopes = [(23, 16), (27, 17), (29, 15), (30, 18)] for slope, offset in steep_slopes: to_add = BinOp( nmlop.MUL, BinOp(nmlop.CMP_EQ, args[0], ConstantNumeric(slope), pos), ConstantNumeric(offset), pos) expr = BinOp(nmlop.ADD, expr, to_add, pos) return expr
def builtin_sound_import(name, args, pos): from nml.actions import action11 if len(args) not in (2, 3): raise generic.ScriptError(name + "() must have 2 or 3 parameters", pos) grfid = parse_string_to_dword(args[0].reduce()) sound_num = args[1].reduce_constant().value volume = args[2].reduce_constant().value if len(args) >= 3 else 100 generic.check_range(volume, 0, 100, "sound volume", pos) return ConstantNumeric(action11.add_sound( (grfid, sound_num, volume), pos), pos)
def signed_byte_parameter(name, args, pos, info): # Convert to a signed byte by AND-ing with 0xFF if len(args) != 1: raise generic.ScriptError("{}() requires one argument, encountered {:d}".format(name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, -128, 127, "parameter of {}()".format(name), pos) ret = nmlop.AND(args[0], 0xFF, pos).reduce() return (ret, [])
def builtin_sound_file(name, args, pos): from nml.actions import action11 if len(args) not in (1, 2): raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) if not isinstance(args[0], StringLiteral): raise generic.ScriptError("Parameter for " + name + "() must be a string literal", pos) volume = args[1].reduce_constant().value if len(args) >= 2 else 100 generic.check_range(volume, 0, 100, "sound volume", pos) return ConstantNumeric(action11.add_sound( (args[0].value, volume), pos), pos)
def _validate_palette(self, name, value): if isinstance(value, expression.SpriteGroupRef): self.palette_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val else: if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "palette", value.pos) self.palette_from_action1 = False return value
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 builtin_palette_1cc(name, args, pos): """ palette_1cc(colour) builtin function. @return Recolour sprite to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '{}'".format(name), args[0].pos) return nmlop.ADD(args[0], 775, pos)
def tile_offset(name, args, pos, info, min, max): if len(args) != 2: raise generic.ScriptError("'{}'() requires 2 arguments, encountered {:d}".format(name, len(args)), pos) for arg in args: if isinstance(arg, expression.ConstantNumeric): generic.check_range(arg.value, min, max, "Argument of '{}'".format(name), arg.pos) x = nmlop.AND(args[0], 0xF) y = nmlop.AND(args[1], 0xF) # Shift y left by four y = nmlop.SHIFT_LEFT(y, 4) param = nmlop.ADD(x, y) #Make sure to reduce the result return ( param.reduce(), [] )
def builtin_create_effect(name, args, pos): """ Builtin function: create_effect(effect_sprite, l_x_offset, t_y_offset, z_offset) Use this to set the values for temporary storages 100+x in the callback create_effect """ if len(args) != 4: raise generic.ScriptError(name + "() must have 4 parameters", pos) from nml import global_constants sprite = args[0].reduce_constant(global_constants.const_list).value offset1 = args[1].reduce_constant().value offset2 = args[2].reduce_constant().value offset3 = args[3].reduce_constant().value generic.check_range(sprite, 0, 255, "effect_sprite in function " + name, args[0].pos) generic.check_range(offset1, -128, 127, "l_x_offset in function " + name, args[1].pos) generic.check_range(offset2, -128, 127, "t_y_offset in function " + name, args[2].pos) generic.check_range(offset3, -128, 127, "z_offset in function " + name, args[3].pos) return ConstantNumeric(sprite | (offset1 & 0xFF) << 8 | (offset2 & 0xFF) << 16 | (offset3 & 0xFF) << 24)
def nearest_house_matching_criterion(name, args, pos, info): # nearest_house_matching_criterion(radius, criterion) # parameter is radius | (criterion << 6) if len(args) != 2: raise generic.ScriptError("{}() requires 2 arguments, encountered {:d}".format(name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, 1, 63, "{}()-parameter 1 'radius'".format(name), pos) if isinstance(args[1], expression.ConstantNumeric) and args[1].value not in (0, 1, 2): raise generic.ScriptError("Invalid value for {}()-parameter 2 'criterion'".format(name), pos) radius = nmlop.AND(args[0], 0x3F, pos) criterion = nmlop.AND(args[1], 0x03, pos) criterion = nmlop.MUL(criterion, 0x40) retval = nmlop.OR(criterion, radius).reduce() return (retval, [])
def builtin_vehicle_curv_info(name, args, pos): """ vehicle_curv_info(prev_cur, cur_next) builtin function @return Value to use with vehicle var curv_info """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for arg in args: if isinstance(arg, ConstantNumeric): generic.check_range(arg.value, -2, 2, "Argument of '{}'".format(name), arg.pos) args = [nmlop.AND(arg, 0xF, pos) for arg in args] cur_next = nmlop.SHIFT_LEFT(args[1], 8) return nmlop.OR(args[0], cur_next)
def _validate_sprite(self, name, value): if isinstance(value, expression.SpriteGroupRef): self.sprite_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val else: self.sprite_from_action1 = False if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "sprite", value.pos) return value if value.supported_by_actionD(raise_error=False): return value self.create_register(name, value) return expression.ConstantNumeric(0)
def check_sprite_size(self): generic.check_range(self.xpos.value, 0, 0x7FFFFFFF, "Real sprite paramater 'xpos'", self.xpos.pos) generic.check_range(self.ypos.value, 0, 0x7FFFFFFF, "Real sprite paramater 'ypos'", self.ypos.pos) generic.check_range(self.xsize.value, 1, 0xFFFF, "Real sprite paramater 'xsize'", self.xsize.pos) generic.check_range(self.ysize.value, 1, 0xFFFF, "Real sprite paramater 'ysize'", self.ysize.pos)
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 cargo_accepted_nearby(name, args, pos, info): # cargo_accepted_nearby(cargo[, xoffset, yoffset]) if len(args) not in (1, 3): raise generic.ScriptError("{}() requires 1 or 3 arguments, encountered {:d}".format(name, len(args)), pos) if len(args) > 1: offsets = args[1:3] for i, offs in enumerate(offsets[:]): if isinstance(offs, expression.ConstantNumeric): generic.check_range(offs.value, -128, 127, "{}-parameter {:d} '{}offset'".format(name, i + 1, "x" if i == 0 else "y"), pos) offsets[i] = nmlop.AND(offs, 0xFF, pos).reduce() # Register 0x100 should be set to xoffset | (yoffset << 8) reg100 = nmlop.OR(nmlop.MUL(offsets[1], 256, pos), offsets[0]).reduce() else: reg100 = expression.ConstantNumeric(0, pos) return (args[0], [(0x100, reg100)])
def builtin_palette_2cc(name, args, pos): """ palette_2cc(colour1, colour2) builtin function. @return Recolour sprite to use """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for i in range(0, 2): if isinstance(args[i], ConstantNumeric): generic.check_range(args[i].value, 0, 15, "Argument of '{}'".format(name), args[i].pos) col2 = nmlop.MUL(args[1], 16, pos) col12 = nmlop.ADD(col2, args[0]) # Base sprite is not a constant base = global_constants.patch_variable("base_sprite_2cc", global_constants.patch_variables["base_sprite_2cc"], pos) return nmlop.ADD(col12, base)
def builtin_relative_coord(name, args, pos): """ relative_coord(x, y) builtin function. @return Coordinates in 0xYYXX format. """ if len(args) != 2: raise generic.ScriptError(name + "() must have x and y coordinates as parameters", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 255, "Argument of '{}'".format(name), args[0].pos) if isinstance(args[1], ConstantNumeric): generic.check_range(args[1].value, 0, 255, "Argument of '{}'".format(name), args[1].pos) x_coord = nmlop.AND(args[0], 0xFF) y_coord = nmlop.AND(args[1], 0xFF) # Shift Y to its position. y_coord = nmlop.SHIFT_LEFT(y_coord, 8) return nmlop.OR(x_coord, y_coord, pos)
def pre_process(self): new_costs = [] for cost in self.costs: cost.value = cost.value.reduce(global_constants.const_list) if isinstance(cost.value, expression.ConstantNumeric): generic.check_range(cost.value.value, -8, 16, 'Base cost value', cost.value.pos) cost.value = expression.BinOp(nmlop.ADD, cost.value, expression.ConstantNumeric(8), cost.value.pos).reduce() if isinstance(cost.name, expression.Identifier): if cost.name.value in base_cost_table: cost.name = expression.ConstantNumeric( base_cost_table[cost.name.value][0]) new_costs.append(cost) elif cost.name.value in generic_base_costs: #create temporary list, so it can be sorted for efficiency tmp_list = [] for num, type in list(base_cost_table.values()): if type == cost.name.value: tmp_list.append( assignment.Assignment( expression.ConstantNumeric(num), cost.value, cost.name.pos)) tmp_list.sort(key=lambda x: x.name.value) new_costs.extend(tmp_list) else: raise generic.ScriptError( "Unrecognized base cost identifier '{}' encountered". format(cost.name.value), cost.name.pos) else: cost.name = cost.name.reduce() if isinstance(cost.name, expression.ConstantNumeric): generic.check_range(cost.name.value, 0, len(base_cost_table), 'Base cost number', cost.name.pos) new_costs.append(cost) self.costs = new_costs
def _validate_bounding_box(self, name, value): if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError( name + " can not be set for ground sprites", value.pos) elif self.type == Action2LayoutSpriteType.CHILD: if name not in ("xoffset", "yoffset"): raise generic.ScriptError( name + " can not be set for child sprites", value.pos) if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, 255, name, value.pos) return value else: assert self.type == Action2LayoutSpriteType.BUILDING if name in ("xoffset", "yoffset", "zoffset"): if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, -128, 127, name, value.pos) return value else: assert name in ("xextent", "yextent", "zextent") if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError( "Value of '{}' must be a compile-time constant number." .format(name), value.pos) generic.check_range(value.value, 0, 255, name, value.pos) return value # Value must be written to a register self.create_register(name, value) if self.type == Action2LayoutSpriteType.BUILDING: # For building sprites, x and y registers are always written together if name == "xoffset" and self.get_register("yoffset") is None: self.create_register("yoffset", expression.ConstantNumeric(0)) if name == "yoffset" and self.get_register("xoffset") is None: self.create_register("xoffset", expression.ConstantNumeric(0)) return expression.ConstantNumeric(0)
def builtin_date(name, args, pos): """ date(year, month, day) builtin function. @return Days since 1 jan 1 of the given date. """ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if len(args) != 3: raise generic.ScriptError("date() requires exactly 3 arguments", pos) from nml import global_constants identifier.ignore_all_invalid_ids = True year = args[0].reduce(global_constants.const_list) identifier.ignore_all_invalid_ids = False try: month = args[1].reduce_constant().value day = args[2].reduce_constant().value except generic.ConstError: raise generic.ScriptError( "Month and day parameters of date() should be compile-time constants", pos) generic.check_range(month, 1, 12, "month", args[1].pos) generic.check_range(day, 1, days_in_month[month - 1], "day", args[2].pos) if not isinstance(year, ConstantNumeric): if month != 1 or day != 1: raise generic.ScriptError( "when the year parameter of date() is not a compile time constant month and day should be 1", pos) #num_days = year*365 + year/4 - year/100 + year/400 part1 = BinOp(nmlop.MUL, year, ConstantNumeric(365)) part2 = BinOp(nmlop.DIV, year, ConstantNumeric(4)) part3 = BinOp(nmlop.DIV, year, ConstantNumeric(100)) part4 = BinOp(nmlop.DIV, year, ConstantNumeric(400)) res = BinOp(nmlop.ADD, part1, part2) res = BinOp(nmlop.SUB, res, part3) res = BinOp(nmlop.ADD, res, part4) return res generic.check_range(year.value, 0, 5000000, "year", year.pos) day_in_year = 0 for i in range(month - 1): day_in_year += days_in_month[i] day_in_year += day if month >= 3 and (year.value % 4 == 0) and ((not year.value % 100 == 0) or (year.value % 400 == 0)): day_in_year += 1 return ConstantNumeric( year.value * 365 + calendar.leapdays(0, year.value) + day_in_year - 1, pos)
def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict): # check the number of parameters num_param = len(sprite.param_list) if num_param == 0: sprite.is_empty = True return sprite elif not (2 <= num_param <= 9): raise generic.ScriptError( "Invalid number of arguments for real sprite. Expected 2..9.", sprite.param_list[0].pos) # create new sprite struct, needed for template expansion new_sprite = RealSprite(poslist=poslist + sprite.poslist) param_offset = 0 if num_param >= 6: # xpos, ypos, xsize and ysize are all optional. If not specified they'll default # to 0, 0, image_width, image_height new_sprite.xpos = sprite.param_list[0].reduce_constant([id_dict]) new_sprite.ypos = sprite.param_list[1].reduce_constant([id_dict]) new_sprite.xsize = sprite.param_list[2].reduce_constant([id_dict]) new_sprite.ysize = sprite.param_list[3].reduce_constant([id_dict]) new_sprite.check_sprite_size() param_offset += 4 new_sprite.xrel = sprite.param_list[param_offset].reduce_constant( [id_dict]) new_sprite.yrel = sprite.param_list[param_offset + 1].reduce_constant( [id_dict]) generic.check_range( new_sprite.xrel.value, -0x8000, 0x7FFF, "Real sprite paramater {:d} 'xrel'".format(param_offset + 1), new_sprite.xrel.pos, ) generic.check_range( new_sprite.yrel.value, -0x8000, 0x7FFF, "Real sprite paramater {:d} 'yrel'".format(param_offset + 2), new_sprite.yrel.pos, ) param_offset += 2 # Next may follow any combination of (flags, filename, mask), but always in that order new_sprite.flags = expression.ConstantNumeric(0) if num_param > param_offset: try: new_sprite.flags = sprite.param_list[param_offset].reduce_constant( [real_sprite_flags, id_dict]) param_offset += 1 except generic.ConstError: # No flags pass new_sprite.file = default_file if num_param > param_offset and not isinstance( sprite.param_list[param_offset], expression.Array): new_sprite.file = sprite.param_list[param_offset].reduce([id_dict]) param_offset += 1 if not isinstance(new_sprite.file, expression.StringLiteral): raise generic.ScriptError( "Real sprite parameter {:d} 'file' should be a string literal". format(param_offset + 1), new_sprite.file.pos, ) if new_sprite.file is None: raise generic.ScriptError("No image file specified for real sprite", sprite.param_list[0].pos) new_sprite.mask_file = default_mask_file new_sprite.mask_pos = None if num_param > param_offset: mask = sprite.param_list[param_offset] param_offset += 1 # Mask may be either string (file only) # or array (empty => no mask, 1 value => file only, 2 => offsets only, 3 => file + offsets) if isinstance(mask, expression.Array): if not (0 <= len(mask.values) <= 3): raise generic.ScriptError( "Real sprite mask should be an array with 0 to 3 values, encountered {:d}" .format(len(mask.values)), mask.pos, ) if len(mask.values) == 0: # disable any default mask new_sprite.mask_file = None else: if len(mask.values) & 1: new_sprite.mask_file = mask.values[0].reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError( "Real sprite parameter 'mask_file' should be a string literal", new_sprite.file.pos) if len(mask.values) & 2: new_sprite.mask_pos = tuple( mask.values[i].reduce_constant([id_dict]) for i in range(-2, 0)) # Check that there is also a mask specified, else the offsets make no sense if new_sprite.mask_file is None: raise generic.ScriptError( "Mask offsets are specified, but there is no mask file set.", new_sprite.mask_pos[0].pos) else: new_sprite.mask_file = mask.reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError( "Real sprite parameter {:d} 'mask' should be an array or string literal" .format(param_offset + 1), new_sprite.file.pos, ) if num_param > param_offset: raise generic.ScriptError( "Real sprite has too many parameters, the last {:d} parameter(s) cannot be parsed." .format(num_param - param_offset), sprite.param_list[param_offset].pos, ) return new_sprite
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()