def add_unit(self, repeat=1, **kwargs): # how many unique units? (units can be repeated, we are using count for numerid ID, so we want uniques) count = len(set(self.units)) if kwargs.get('type', None) is not None: unit = kwargs['type'](consist=self, **kwargs) else: unit = RoadVehicle(consist=self, **kwargs) if count == 0: unit.id = self.id # first vehicle gets no numeric id suffix - for compatibility with buy menu list ids etc else: unit.id = self.id + '_' + str(count) unit.numeric_id = self.get_and_verify_numeric_id(count) if self.semi_truck_so_redistribute_capacity: if count == 0 and kwargs.get('capacity', 0) != 0: # guard against lead unit having capacity set in declared props (won't break, just wrong) utils.echo_message("Error: " + self.id + ". First unit of semi-truck must have capacity 0") if count == 1: # semi-trucks need some capacity moved to lead unit to gain sufficient TE # this automagically does that, allowing capacities to be defined simply on the trailer in the vehicle definition # sometimes a greater good requires a small evil, although this will probably go wrong eh? if repeat != 1: # guard against unintended application of this to anything except first trailer utils.echo_message("Error: " + self.id + ". Semi-truck cannot repeat first trailer in consist") specified_capacities = unit.capacities unit.capacities = [int(math.floor(0.5 * capacity)) for capacity in specified_capacities] self.units[0].capacities = [int(math.ceil(0.5 * capacity)) for capacity in specified_capacities] for repeat_num in range(repeat): self.units.append(unit)
def get_ships_in_buy_menu_order(): ships = [] # first compose the buy menu order list buy_menu_sort_order = [] if repo_vars.get('roster', '*') == '*': active_rosters = [roster.id for roster in registered_rosters] else: active_rosters = [repo_vars['roster']] # make sure it's iterable for roster in registered_rosters: if roster.id in active_rosters: buy_menu_sort_order.extend(roster.buy_menu_sort_order) ships.extend(roster.ships_in_buy_menu_order) # now guard against any ships missing from buy menu order or vice versa, as that wastes time asking 'wtf?' when they don't appear in game ship_id_defender = set([ship.id for ship in ships]) buy_menu_defender = set(buy_menu_sort_order) for id in buy_menu_defender.difference(ship_id_defender): utils.echo_message( "Warning: ship " + id + " in buy_menu_sort_order, but not found in registered ships") for id in ship_id_defender.difference(buy_menu_defender): utils.echo_message( "Warning: ship " + id + " in ships, but not in buy_menu_sort_order - won't show in game") return ships
def get_industry_name(self, industry, economy=None): # industries don't store the name directly as a python attr, but in lang - so look it up in base_lang using string id name = industry.get_property('name', economy) string_id = utils.unwrap_nml_string_declaration(name) if string_id not in base_lang_strings: utils.echo_message('Warning: string ' + string_id + ' missing for docs') return base_lang_strings.get(string_id, 'NO NAME ' + str(name) + ' ' + industry.id)
def weight(self): mult = self.weight_multiplier # trams are 10% heavier per capacity if self.roadveh_flag_tram: mult = mult + 0.1 consist_weight = mult * self.total_capacities[1] if consist_weight > 63: utils.echo_message("Error: consist weight is " + str(consist_weight) + "t for " + self.id + "; must be < 63t") return min(consist_weight, 63)
def get_reduced_set_of_variant_dates(self): # find all the unique dates that will need a switch constructing years = set() for variant in self.model_variants: years.update((variant.intro_date, variant.end_date)) years = sorted(years) # quick integrity check if years[0] != 0: utils.echo_message(self.id + " doesn't have at least one model variant with intro date 0 (required for nml switches to work)") return years
def get_and_verify_numeric_id(self, offset): numeric_id = self.base_numeric_id + offset # guard against the ID being too large to build in an articulated consist if numeric_id > 16383: utils.echo_message("Error: numeric_id " + str(numeric_id) + " for " + self.id + " can't be used (16383 is max ID for articulated vehicles)") # non-blocking guard on duplicate IDs for id in numeric_id_defender: if id == numeric_id: utils.echo_message("Error: consist " + self.id + " unit id collides (" + str(numeric_id) + ") with units in another consist") numeric_id_defender.append(numeric_id) return numeric_id
def __init__(self, id, **kwargs): self.id = id self.cargo_label = kwargs["cargo_label"] self.type_name = kwargs["type_name"] self.unit_name = kwargs["unit_name"] self.type_abbreviation = kwargs["type_abbreviation"] self.sprite = kwargs["sprite"] self.weight = kwargs["weight"] self.is_freight = kwargs["is_freight"] self.cargo_classes = kwargs["cargo_classes"] self.town_growth_effect = kwargs["town_growth_effect"] self.town_growth_multiplier = kwargs["town_growth_multiplier"] self.units_of_cargo = kwargs["units_of_cargo"] self.items_of_cargo = kwargs["items_of_cargo"] self.penalty_lowerbound = kwargs["penalty_lowerbound"] self.single_penalty_length = kwargs["single_penalty_length"] self.price_factor = kwargs["price_factor"] self.capacity_multiplier = kwargs["capacity_multiplier"] # not nml properties self.allow_animated_pixels = kwargs.get( "allow_animated_pixels", False) # suppress nml warnings about animated pixels self.icon_indices = kwargs[ "icon_indices"] # icon indices relate to position of icon in cargo icons spritesheet self.economy_variations = {} for economy in registered_economies: if self.id in economy.cargo_ids: # create an economy variation numeric_id = economy.cargo_ids.index(self.id) # As of May 2015, OTTD requires some cargos in specific slots, otherwise default houses break mandatory_numeric_ids = { "PASS": 0, "MAIL": 2, "GOOD": 5, "FOOD": 11 } for key, value in mandatory_numeric_ids.items(): if self.cargo_label == key and numeric_id != value: raise Exception("Economy " + economy.id + ": has cargo " + self.id + " in position " + str(numeric_id) + "; needs to be in position " + str(value)) self.economy_variations[economy] = {"numeric_id": numeric_id} # guard against overlapping icon indices, icons should be unique per cargo # if two cargos use same icon (1) don't, copy-paste, then adjust some pixels for one of them (2) see 1 for cargo in registered_cargos: if cargo.icon_indices == self.icon_indices: utils.echo_message( "Cargo " + self.id + " has overlapping icon_indices with cargo " + cargo.id)
def get_engine_cost_points(self): # Up to 20 points for power. 1 point per 100hp # Power is therefore capped at 2000hp by design, this isn't a hard limit, but raise a warning if self.power > 2000: utils.echo_message("Consist " + self.id + " has power > 2000hp, which is too much") power_cost_points = self.power / 100 # Up to 30 points for speed above up to 90mph. 1 point per 3mph if self.speed > 90: utils.echo_message("Consist " + self.id + " has speed > 90, which is too much") speed_cost_points = min(self.speed, 90) / 3 # Up to 20 points for intro date after 1870. 1 point per 8 years. # Intro dates capped at 2030, this isn't a hard limit, but raise a warning if self.intro_date > 2030: utils.echo_message("Consist " + self.id + " has intro_date > 2030, which is too much") date_cost_points = max((self.intro_date - 1870), 0) / 8 # Up to 20 points for capacity. 1 point per 8t. # Capacity capped at 160, this isn't a hard limit, but raise a warning if self.total_capacities[1] > 160: utils.echo_message("Consist " + self.id + " has capacity > 160, which is too much") consist_capacity_points = min(self.total_capacities[1], 160) return power_cost_points + speed_cost_points + date_cost_points + consist_capacity_points
def __init__(self, **kwargs): self.id = kwargs.get('id', None) self.vehicle_module_path = inspect.stack()[2][1] # setup properties for this consist (props either shared for all vehicles, or placed on lead vehicle of consist) self._name = kwargs.get('name', None) # private as 'name' is an @property method to add type substring self.base_numeric_id = kwargs.get('base_numeric_id', None) self.road_type = kwargs.get('road_type', None) self.tram_type = kwargs.get('tram_type', None) if self.road_type is not None and self.tram_type is not None: utils.echo_message("Error: " + self.id + ". Vehicles must not have both road_type and tram_type properties set. Set one of these only") self.roadveh_flag_tram = True if self.tram_type is not None else None self.intro_date = kwargs.get('intro_date', None) self.vehicle_life = kwargs.get('vehicle_life', None) self._power = kwargs.get('power', None) # sound effects are faff, they can be set by consist subclass (Bus vs. Coach), or will fall back to a default sound for the vehicle subclass (by power type) self._sound_effect = None self.default_sound_effect = None # set by unit subclasses # suffix for 'Diesel', 'Steam' etc in name string, set by unit subclasses, but stored in consist as it's a consist property self._power_type_suffix = None # option for multiple default cargos, cascading if first cargo(s) are not available self.default_cargos = [] # semi-trucks need some redistribution of capacity to get correct TE (don't use this of other magic, bad idea) self.semi_truck_so_redistribute_capacity = kwargs.get('semi_truck_so_redistribute_capacity', False) self._speed = kwargs.get('speed', None) self.class_refit_groups = [] self.label_refits_allowed = [] # no specific labels needed self.label_refits_disallowed = [] self.autorefit = False self.loading_speed_multiplier = kwargs.get('loading_speed_multiplier', 1) self.cargo_age_period = kwargs.get('cargo_age_period', global_constants.CARGO_AGE_PERIOD) # arbitrary adjustments of points that can be applied to adjust buy cost and running cost, over-ride in consist as needed # values can be -ve or +ve to dibble specific vehicles (but total calculated points cannot exceed 255) self.type_base_buy_cost_points = kwargs.get('type_base_buy_cost_points', 0) self.type_base_running_cost_points = kwargs.get('type_base_running_cost_points', 0) # multiplier of capacity, used to set consist weight, over-ride in vehicle sub-class as needed # set this to the value for road vehicles...trams will be automatically adjusted self.weight_multiplier = 0.4 # create structure to hold the units self.units = [] # create a structure for cargo /livery graphics options self.gestalt_graphics = GestaltGraphics() # roster is set when the vehicle is registered to a roster, only one roster per vehicle self.roster_id = None
def pretty_print_cargo_classes(self, cargo): result = [] pretty_names = {'CC_PASSENGERS': 'Passengers', 'CC_MAIL': 'Mail', 'CC_EXPRESS': 'Express', 'CC_ARMOURED': 'Armoured', 'CC_BULK': 'Bulk (uncountable)', 'CC_PIECE_GOODS': 'Piece Goods (countable)', 'CC_LIQUID': 'Liquid', 'CC_REFRIGERATED': 'Refrigerated', 'CC_HAZARDOUS': 'Hazardous', 'CC_COVERED': 'Covered (weather protected)', 'CC_NON_POURABLE': 'Not Pourable', } cargo_classes_as_literal = cargo.cargo_classes[8:-1] cargo_classes = [i.strip() for i in cargo_classes_as_literal.split(',')] for cargo_class in cargo_classes: if cargo_class not in pretty_names: utils.echo_message('cargo class ' + cargo_class + ' is not pretty-printable (used in cargo ' + cargo.id + ')') else: result.append(pretty_names[cargo_class]) return ', '.join(result)
def generic_rows(self): utils.echo_message( 'generic_rows not implemented in GestaltGraphicsCargoSpecificLivery (by design)' ) return None
def assert_cargo_labels(self, cargo_labels): for i in cargo_labels: if i not in global_constants.cargo_labels: utils.echo_message("Warning: vehicle " + self.id + " references cargo label " + i + " which is not defined in the cargo table")