Beispiel #1
0
 def test_as_json(self):
     '''Test planet.as_json() exporter'''
     planet = Planet()
     jdata = planet.as_json()
     print(jdata)
     p_data = json.loads(jdata)
     self.assertTrue(p_data['uwp'] == planet.uwp())
     self.assertTrue(p_data['trade_codes'] == planet.trade_codes)
     self.assertTrue(p_data['travel_code'] == planet.travel_code)
     self.assertTrue(p_data['bases'] == planet.bases)
     self.assertTrue(p_data['orbit'] == planet.orbit)
     self.assertTrue(p_data['is_mainworld'] == planet.is_mainworld)
Beispiel #2
0
 def test_json_import(self):
     '''Test planet.json_import() importer'''
     jdata = u'{"trade_codes": ["Fl", "Lo"], "travel_code": "", ' +\
         '"is_mainworld": true, "uwp": "D9A6313-3", "orbit": 5, ' +\
         '"bases": "", "mainworld_type": "Planet", ' +\
         '"parent_type": null, "orbit_around_parent": null  }'
     p_data = json.loads(jdata)
     planet = Planet()
     planet.json_import(jdata)
     self.assertTrue(p_data['uwp'] == planet.uwp())
     self.assertTrue(p_data['trade_codes'] == planet.trade_codes)
     self.assertTrue(p_data['travel_code'] == planet.travel_code)
     self.assertTrue(p_data['bases'] == planet.bases)
     self.assertTrue(p_data['orbit'] == planet.orbit)
     self.assertTrue(p_data['is_mainworld'] == planet.is_mainworld)
Beispiel #3
0
class System(object):
    '''Return a T5 basic system with the specified name and location hex'''

    naval_base_presence = Table()
    naval_base_presence.add_row('A', 6)
    naval_base_presence.add_row('B', 5)

    scout_base_presence = Table()
    scout_base_presence.add_row('A', 4)
    scout_base_presence.add_row('B', 5)
    scout_base_presence.add_row('C', 6)
    scout_base_presence.add_row('D', 7)

    mw_orbit_flux_table = Table()
    mw_orbit_flux_table.add_row(-6, -2)
    mw_orbit_flux_table.add_row((-5, -3), -1)
    mw_orbit_flux_table.add_row((-2, 2), 0)
    mw_orbit_flux_table.add_row((3, 5), 1)
    mw_orbit_flux_table.add_row(6, 2)

    def __init__(self, name='', location_hex='0000'):
        self.hex = location_hex
        self.name = name
        self.zone = ''
        self.stellar = Primary()
        self.mainworld = Planet()
        self.determine_mw_orbit()

        self.bases = self.determine_bases()
        self.pbg = Pbg(self.mainworld)
        self.allegiance = 'Na'
        self.determine_trade_codes()
        self.determine_x()
        self.nobility = self.determine_nobility()
        self.num_worlds = (self.pbg.belts + self.pbg.gasgiants + D6.roll(1) +
                           1)
        self.determine_travel_zone()

    def display(self):
        '''Display'''
        return '{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}'.format(
            self.hex, self.name, self.mainworld.uwp(),
            ' '.join(self.mainworld.trade_codes), str(self.importance_x),
            str(self.economic_x),
            str(self.cultural_x), self.nobility, self.bases, self.zone,
            str(self.pbg), self.num_worlds, self.allegiance,
            self.stellar.display())

    def __str__(self):
        oformat = '{0:4} {1:20} {2:9} {3:18} {4:4} {5:7} {6:6} ' +\
            '{7:7} {8:2} {9:1} {10:3} {11:2} {12:2} {13:14}'
        return oformat.format(self.hex, self.name, self.mainworld.uwp(),
                              ' '.join(self.mainworld.trade_codes),
                              str(self.importance_x), str(self.economic_x),
                              str(self.cultural_x.display()),
                              self.nobility, self.bases, self.zone,
                              str(self.pbg), self.num_worlds, self.allegiance,
                              self.stellar.display())

    def determine_nobility(self):
        '''Determine noble representation'''
        nobility = 'B'  # Every world gets a knight
        # Baronet
        if ('Pa' in self.mainworld.trade_codes
                or 'Pr' in self.mainworld.trade_codes):
            nobility += 'c'
        # Baron
        if ('Ag' in self.mainworld.trade_codes
                or 'Ri' in self.mainworld.trade_codes):
            nobility += 'C'
        if 'Pi' in self.mainworld.trade_codes:
            nobility += 'D'  # Marquis
        if 'Ph' in self.mainworld.trade_codes:
            nobility += 'e'  # Viscount
        # Count
        if ('In' in self.mainworld.trade_codes
                or 'Hi' in self.mainworld.trade_codes):
            nobility += 'E'
        if int(self.importance_x) >= 4:
            nobility += 'f'  # Duke
        return nobility

    def determine_bases(self):
        '''Determine bases'''
        bases = ''
        # Naval base
        target = self.naval_base_presence.lookup(self.mainworld.starport)
        if target is not None:
            if D6.roll(2) <= target:
                bases += 'N'
        # Scout base
        target = self.scout_base_presence.lookup(self.mainworld.starport)
        if target is not None:
            if D6.roll(2) <= target:
                bases += 'S'
        return bases

    def determine_trade_codes(self):
        '''Determine climate trade codes'''
        tcs = TradeCodes(self.mainworld, self)
        self.mainworld.trade_codes = tcs.generate()

    def determine_mw_orbit(self):
        '''Determine mainworld orbit'''
        orbit = self.stellar.habitable_zone +\
            self.mw_orbit_flux_table.lookup(FLUX.flux())
        orbit = max(orbit, 0)
        self.mainworld.orbit = orbit

    def determine_travel_zone(self, starport_x_is_red=True):
        '''Determine travel zone - A or R'''
        self.zone = ''
        if int(self.mainworld.government) + \
                int(self.mainworld.law_level) in [20, 21]:
            self.zone = 'A'
            self.mainworld.trade_codes.append('Da')
        elif int(self.mainworld.government) + \
                int(self.mainworld.law_level) > 22:
            self.zone = 'R'
        if starport_x_is_red:
            if self.mainworld.starport == 'X':
                self.zone = 'R'
                self.mainworld.trade_codes.append('Fo')

    def as_json(self):
        '''Return JSON representation of system'''
        system_dict = {
            'name': self.name,
            'hex': self.hex,
            'stellar': self.stellar.as_json(),
            'mainworld': self.mainworld.as_json(),
            'bases': self.bases,
            'pbg': str(self.pbg),
            'allegiance': self.allegiance,
            'Ix': str(self.importance_x),
            'Ex': str(self.economic_x),
            'Cx': str(self.cultural_x),
            'nobility': self.nobility,
            'worlds': self.num_worlds,
            'zone': self.zone
        }
        return json.dumps(system_dict)

    def json_import(self, jdata):
        '''Import from JSON'''
        system_dict = json.loads(jdata)
        self.name = system_dict['name']
        self.hex = system_dict['hex']
        self.bases = system_dict['bases']
        self.allegiance = system_dict['allegiance']
        self.nobility = system_dict['nobility']
        self.num_worlds = int(system_dict['worlds'])
        self.zone = system_dict['zone']
        self.stellar.json_import(system_dict['stellar'])
        self.mainworld.json_import(system_dict['mainworld'])
        self.pbg.json_import(system_dict['pbg'])
        self.importance_x.json_import(system_dict['Ix'])
        self.economic_x.json_import(system_dict['Ex'])
        self.cultural_x.json_import(system_dict['Cx'])

    def determine_x(self):
        '''Determine Ix Ex Cx values'''
        self.importance_x = ImportanceExtension(self.mainworld, self)
        self.economic_x = EconomicExtension(self.pbg,
                                            int(self.mainworld.population),
                                            int(self.mainworld.tech_level),
                                            self.mainworld.trade_codes,
                                            int(self.importance_x))
        self.cultural_x = CulturalExtension(int(self.mainworld.population),
                                            int(self.importance_x),
                                            int(self.mainworld.tech_level))
Beispiel #4
0
class TradeCargo(object):
    '''Spec cargo object'''
    def __init__(self):
        self.cost = 0
        self.description = ''
        self.interesting_trade_codes = []
        self.actual_value = 0
        self.price = 0
        self._codes = {
            'Ga': [[
                'Bulk Protein', 'Bulk Carbs', 'Bulk Fats', 'Bulk Pharma',
                'Livestock', 'Seedstock'
            ],
                   [
                       'Flavored Waters', 'Wines', 'Juices', 'Nectars',
                       'Decoctions', 'Drinkable Lymphs'
                   ],
                   [
                       'Health Foods', 'Nutraceuticals', 'Fast Drug',
                       'Painkillers', 'Antiseptic', 'Antibiotics'
                   ],
                   [
                       'Incenses', 'Iridescents', 'Photonics', 'Pigments',
                       'Noisemakers', 'Soundmakers'
                   ],
                   [
                       'Fine Furs', 'Meat Delicacies', 'Fruit Delicacies',
                       'Candies', 'Textiles', 'Exotic Sauces'
                   ], ['_As', '_De', '_Fl', '_Ic', '_Na', '_In']],
            'Fa': [[
                'Bulk Woods', 'Bulk Pets', 'Bulk Herbs', 'Bulk Spices',
                'Bulk Nitrates', 'Foodstuffs'
            ],
                   [
                       'Flowers', 'Aromatics', 'Pheromones', 'Secretions',
                       'Adhesives', 'Novel Flavorings'
                   ],
                   [
                       'Antifungals', 'Antivirals', 'Panacea', 'Pseudomones',
                       'Anagathics', 'Slow Drug'
                   ],
                   [
                       'Strange Seeds', 'Motile Plants', 'Reactive Plants',
                       'IR Emitters', 'Lek Emitters'
                   ],
                   [
                       'Spices', 'Organic Gems', 'Flavorings', 'Aged Meats',
                       'Fermented Fluids', 'Fine Aromatics'
                   ], ['_Po', '_Ri', '_Va', '_Ic', '_Na', '_In']],
            'As':
            [[
                'Bulk Nitrates', 'Bulk Carbon', 'Bulk Iron', 'Bulk Copper',
                'Radioactive Ores', 'Bulk Ices'
            ], ['Ores', 'Ices', 'Carbons', 'Metals', 'Uranium', 'Chelates'],
             ['Platinum', 'Gold', 'Gallium', 'Silver', 'Thorium', 'Radium'],
             [
                 'Unusual Rocks', 'Fused Metals', 'Strange Crystals',
                 'Fine Dusts', 'Magnetics', 'Light-Sensitives'
             ],
             [
                 'Gemstones', 'Alloys', 'Iridium Sponge', 'Lanthanum',
                 'Isotopes', 'Anti-Matter'
             ], ['_Ag', '_De', '_Na', '_Po', '_Ri', '_Ic']],
            'De': [[
                'Bulk Nitrates', 'Bulk Minerals', 'Bulk Abrasives',
                'Bulk Particulates', 'Exotic Fauna', 'Exotic Flora'
            ],
                   [
                       'Archeologicals', 'Fauna', 'Flora', 'Minerals',
                       'Ephemerals', 'Polymers'
                   ],
                   [
                       'Stimulants', 'Bulk Herbs', 'Palliatives', 'Pheromones',
                       'Antibiotics', 'Combat Drug'
                   ],
                   [
                       'Envirosuits', 'Reclamation Suits', 'Navigators',
                       'Dupe Masterpieces', 'ShimmerCloth', 'ANIFX Blocker'
                   ],
                   [
                       'Excretions', 'Flavorings', 'Nectars', 'Pelts',
                       'ANIFX Dyes', 'Seedstock'
                   ],
                   [
                       'Pheromones', 'Artifacts', 'Sparx', 'Repulsant',
                       'Dominants', 'Fossils'
                   ]],
            'Fl': [[
                'Bulk Carbon', 'Bulk Petros', 'Bulk Precipitates',
                'Exotic Fluids', 'Organic Polymers', 'Bulk Synthetics'
            ],
                   [
                       'Archeologicals', 'Fauna', 'Flora', 'Germanes', 'Flill',
                       'Chelates'
                   ],
                   [
                       'Antifungals', 'Antivirals', 'Palliatives',
                       'Counter-prions', 'Antibiotics', 'Cold Sleep Pills'
                   ],
                   [
                       'Silanes', 'Lek Emitters', 'Aware Blockers',
                       'Soothants', 'Self-Solving Puzzlies',
                       'Fluidic Timepieces'
                   ],
                   [
                       'Flavorings', 'Unusual Fluids', 'Encapsulants',
                       'Insidiants', 'Corrosives', 'Exotic Aromatics'
                   ], ['_In', '_Ri', '_Ic', '_Na', '_Ag', '_Po']],
            'Ic': [[
                'Bulk Ices', 'Bulk Precipitates', 'Bulk Ephemerals',
                'Exotic Flora', 'Bulk Gases', 'Bulk Oxygen'
            ],
                   [
                       'Archeologicals', 'Fauna', 'Flora', 'Minerals',
                       'Luminescents', 'Polymers'
                   ],
                   [
                       'Antifungals', 'Antivirals', 'Palliatives',
                       'Counter-prions', 'Antibiotics', 'Cold Sleep Pills'
                   ],
                   [
                       'Silanes', 'Lek Emitters', 'Aware Blockers',
                       'Soothants', 'Self-Solving Puzzlies',
                       'Fluidic Timepieces'
                   ],
                   [
                       'Unusual Ices', 'Cryo Alloys', 'Rare Minerals',
                       'Unusual Fluids', 'Cryogems', 'VHDUS Dyes'
                   ],
                   [
                       'Fossils', 'Cryogems', 'Vision Suppressant',
                       'Fission Suppressant', 'Wafers', 'Cold Sleep Pills'
                   ]],
            'In': [[
                'Electronics', 'Photonics', 'Magnetics', 'Fluidics',
                'Polymers', 'Gravitics'
            ],
                   [
                       'Obsoletes', 'Used Goods', 'Reparables', 'Radioactives',
                       'Metals', 'Sludges'
                   ],
                   [
                       'Biologics', 'Mechanicals', 'Textiles', 'Weapons',
                       'Armor', 'Robots'
                   ],
                   [
                       'Nostrums', 'Restoratives', 'Palliatives', 'Chelates',
                       'Antidotes', 'Antitoxins'
                   ],
                   [
                       'Software', 'Databases', 'Expert Systems', 'Upgrades',
                       'Backups', 'Raw Sensings'
                   ],
                   [
                       'Disposables', 'Respirators', 'Filter Masks',
                       'Combination', 'Parts', 'Improvements'
                   ]],
            'Na': [[
                'Bulk Abrasives', 'Bulk Gases', 'Bulk Minerals',
                'Bulk Precipitates', 'Exotic Fauna', 'Exotic Flora'
            ],
                   [
                       'Archeologicals', 'Fauna', 'Flora', 'Minerals',
                       'Ephemerals', 'Polymers'
                   ],
                   [
                       'Branded Tools', 'Drinkable Lymphs', 'Strange Seeds',
                       'Pattern Creators', 'Pigments', 'Warm Leather'
                   ],
                   [
                       'Hummingsand', 'Masterpieces', 'Fine Carpets',
                       'Isotopes', 'Pelts', 'Seedstock'
                   ],
                   [
                       'Masterpieces', 'Unusual Rocks', 'Artifacts',
                       'Non-fossil Carcasses', 'Replicating Clays',
                       'ANIFX EMitter'
                   ], ['_Ag', '_Ri', '_In', '_Ic', '_De', '_Fl']],
            'Po': [[
                'Bulk Nutrients', 'Bulk Fibers', 'Bulk Organics',
                'Bulk Minerals', 'Bulk Textiles', 'Exotic Flora'
            ],
                   [
                       'Art', 'Recordings', 'Writings', 'Tactiles',
                       'Osmancies', 'Wafers'
                   ],
                   [
                       'Strange Crystals', 'Strange Seeds', 'Pigments',
                       'Emotion Lighting', 'Silanes', 'Flora'
                   ],
                   [
                       'Gemstones', 'Antiques', 'Collectibles', 'Allotropes',
                       'Spices', 'Seedstock'
                   ],
                   [
                       'Masterpieces', 'Exotic Flora', 'Antiques',
                       'Incomprehensibles', 'Fossils', 'VHDUS Emitter'
                   ], ['_In', '_Ri', '_Fl', '_Ic', '_Ag', '_Va']],
            'Ri': [[
                'Bulk Foodstuffs', 'Bulk Protein', 'Bulk Carbs', 'Bulk Fats',
                'Exotic Flora', 'Exotic Fauna'
            ],
                   [
                       'Echostones', 'Self-Defenders', 'Attractants',
                       'Sophont Cuisine', 'Sophone Hats', 'Variable Tattoos'
                   ],
                   [
                       'Branded Foods', 'Branded Drinks', 'Branded Clothes',
                       'Flavored Drinks', 'Flowers', 'Music'
                   ],
                   [
                       'Delicacies', 'Spices', 'Tisanes', 'Nectars', 'Pelts',
                       'Variable Tattoos'
                   ],
                   [
                       'Antique Art', 'Masterpieces', 'Artifacts', 'Fine Art',
                       'Meson Barriers', 'Famous Wafers'
                   ],
                   [
                       'Edutainments', 'Recordings', 'Writings', 'Tactiles',
                       'Osmancies', 'Wafers'
                   ]],
            'Va': [[
                'Bulk Dusts', 'Bulk Minerals', 'Bulk Metals',
                'Radioactive Ores', 'Bulk Particulates', 'Ephererals'
            ],
                   [
                       'Branded Vacc Suits', 'Awareness Pinger',
                       'Strange Seeds', 'Pigments', 'Unusual Minerals',
                       'Exotic Crystals'
                   ],
                   [
                       'Branded Oxygen', 'Vacc Suit Scents',
                       'Vacc Suit Patches', 'Branded Tools', 'Holo-Companions',
                       'Flavored Air'
                   ],
                   [
                       'Vacc Gems', 'Unusual Dusts', 'Insulants',
                       'Crafted Devices', 'Rare Minerals', 'Catalysts'
                   ],
                   [
                       'Archeologicals', 'Fauna', 'Flora', 'Minerals',
                       'Ephemerals', 'Polymers'
                   ],
                   [
                       'Obsoletes', 'Used Goods', 'Reparables', 'Plutonium',
                       'Metals', 'Sludges'
                   ]],
            'Cp': [[
                'Software', 'Expert Systems', 'Databases', 'Upgrades',
                'Backups', 'Raw Sensings'
            ],
                   [
                       'Incenses', 'Contemplatives', 'Cold Welders',
                       'Polymer Sheets', 'Hats', 'Skin Tones'
                   ],
                   [
                       'Branded Clothes', 'Branded Devices', 'Flavored Drinks',
                       'Flavorings', 'Decorations', 'Group Symbols'
                   ],
                   [
                       'Monumental Art', 'Holo Sculpture', 'Collectible Books',
                       'Jewelry', 'Museum Items', 'Monumental Art'
                   ],
                   [
                       'Coinage', 'Currency', 'Money Cards', 'Gold', 'Silver',
                       'Platinum'
                   ],
                   [
                       'Regulations', 'Synchronzations', 'Expert Systems',
                       'Educationals', 'Mandates', 'Accountings'
                   ]]
        }
        self.source_world = Planet()
        self.market_world = None
        self.actual_value_rolls = (None, None)
        self.broker_skill = None
        self.broker_dm = None
        self.commission = 0
        self.net_actual_value = 0
        seed()

    def generate_cargo(self, source_uwp, market_uwp=None, broker_skill=0):
        '''Generate cargo'''
        try:
            self.source_world._load_uwp(source_uwp)  # noqa
        except (ValueError, TypeError):
            raise ValueError('Invalid source UWP {}'.format(source_uwp))
        try:
            assert int(broker_skill) >= 0
            self.broker_skill = int(broker_skill)
        except TypeError:
            raise ValueError('Invalid broker_skill {}'.format(broker_skill))
        self.source_world.mainworld_type = None
        self.source_world.determine_trade_codes()
        self.source_world.trade_codes = self.purge_ce_trade_codes(
            self.source_world.trade_codes)
        self.description = self.select_cargo_name()
        self.determine_cost(self.source_world.trade_codes)
        self.add_detail(self.source_world.trade_codes)

        if market_uwp is not None:
            self.market_world = Planet()
            try:
                self.market_world._load_uwp(market_uwp)  # noqa
            except ValueError:
                raise ValueError('Invalid market UWP {}'.format(market_uwp))
            self.market_world.mainworld_type = None
            self.market_world.determine_trade_codes()
            self.market_world.trade_codes = self.purge_ce_trade_codes(
                self.market_world.trade_codes)
            self.determine_price()

    def select_cargo_name(self, add_detail_flag=True):
        '''Select cargo based on [trade_codes]'''
        # Pick trade code at random
        LOGGER.debug('Supplied trade codes = %s',
                     self.source_world.trade_codes)
        if self.source_world.trade_codes == []:
            LOGGER.debug('No trade codes supplied, using Na')
            trade_code = 'Na'
        else:
            LOGGER.debug('randint = %s',
                         randint(0,
                                 len(self.source_world.trade_codes) - 1))
            trade_code = self.source_world.trade_codes[randint(
                0,
                len(self.source_world.trade_codes) - 1)]
        LOGGER.debug('Selected trade code %s', trade_code)

        # Ag => pick either Ga or Fa at random
        if trade_code == 'Ag':
            LOGGER.debug('Trade code is Ag, select either Ga or Fa')
            trade_code = ('Fa' if randint(0, 1) else 'Ga')
            LOGGER.debug('Trade code is %s', trade_code)

        # Validate trade code -- use Na if it's not in the list
        if trade_code not in self._codes:
            LOGGER.debug('%s not in supported trade codes, using Na instead',
                         trade_code)
            trade_code = 'Na'

        # Pick cargo at random
        LOGGER.debug('Picking cargo description for %s', trade_code)
        cargo = self._codes[trade_code][randint(0, 5)][randint(0, 5)]
        LOGGER.debug('Selected %s', cargo)

        # Deal with imbalance results (_Xx)
        if cargo.startswith('_'):
            LOGGER.debug('Imbalance cargo %s', cargo)
            add_detail_flag = False
            code = cargo.replace('_', '')
            LOGGER.debug('Rerunning with imbalance')
            cargo = self.select_cargo_name([code])

        # Classification-specific prefix
        prefix = None
        if add_detail_flag:
            prefix = self.add_detail([trade_code])
        if prefix:
            cargo = '{} {}'.format(prefix, cargo)
        return cargo

    @staticmethod
    def add_detail(trade_codes):
        '''Add detail prefix based on trade code'''
        LOGGER.debug('Supplied trade_codes = %s', trade_codes)
        prefixes = {
            'As': 'Strange',
            'Ba': 'Gathered',
            'De': 'Mineral',
            'Di': 'Artifact',
            'Fl': 'Unusual',
            'Ga': 'Premium',
            'He': 'Strange',
            'Hi': 'Processed',
            'Ic': 'Cryo',
            'Ni': 'Unprocessed',
            'Po': 'Obscure',
            'Ri': 'Quality',
            'Va': 'Exotic',
            'Wa': 'Infused'
        }

        descriptions = []
        for code in trade_codes:
            LOGGER.debug('Processing code %s', code)
            if code in prefixes:
                LOGGER.debug('Found description %s for code %s',
                             prefixes[code], code)
                descriptions.append(prefixes[code])
        LOGGER.debug('Available descriptions = %s', descriptions)

        # Weed out In/Processed
        if 'Processed' in descriptions and 'In' in trade_codes:
            descriptions.remove('Processed')
        # Weed out As/Exotic
        if 'Exotic' in descriptions and 'As' in trade_codes:
            descriptions.remove('Exotic')

        # Pick one
        if descriptions:
            resp = descriptions[randint(0, len(descriptions) - 1)]
        else:
            resp = None
        return resp

    def determine_cost(self, trade_codes):
        ''' Process trade codes - add valid TCs to self.trade_codes'''
        cost_mods = {
            'Ag': -1000,
            'As': -1000,
            'Ba': +1000,
            'De': +1000,
            'Fl': +1000,
            'Hi': -1000,
            'Ic': 0,
            'In': -1000,
            'Lo': +1000,
            'Na': 0,
            'Ni': +1000,
            'Po': -1000,
            'Ri': +1000,
            'Va': +1000
        }
        self.cost = 3000
        self.cost += 100 * int(self.source_world.tech_level)
        LOGGER.debug('Cost = %s', self.cost)
        if trade_codes == []:
            trade_codes = ['Na']
        valid_trade_codes = []
        for trade_code in trade_codes:
            if trade_code in cost_mods.keys():
                LOGGER.debug('Adding trade code %s', trade_code)
                valid_trade_codes.append(trade_code)
            else:
                LOGGER.debug('Ignoring trade code %s', trade_code)
        self.interesting_trade_codes = sorted(list(set(valid_trade_codes)))

        # Add cost modifiers
        for trade_code in self.interesting_trade_codes:
            LOGGER.debug('Processing trade code %s (cost mod = Cr %s)',
                         trade_code, cost_mods[trade_code])
            self.cost += cost_mods[trade_code]

    def determine_price(self):
        '''Determine price based on source TCs, market TCs'''
        market_mods = {
            'Ag': (['Ag', 'As', 'De', 'Hi', 'In', 'Ri', 'Va'], 1000),
            'As': (['As', 'In', 'Ri', 'Va'], 1000),
            'Ba': (['In'], 1000),
            'De': (['De'], 1000),
            'Fl': (['Fl'
                    'In'], 1000),
            'Hi': (['Hi'], 1000),
            'In': (['Ag', 'As', 'De', 'Fl', 'Hi', 'In', 'Ri', 'Va'], 1000),
            'Na': (['As', 'De', 'Va'], 1000),
            'Po': (['Ag', 'Hi', 'In', 'Ri'], -1000),
            'Ri': (['Ag', 'De', 'Hi', 'In', 'Ri'], 1000),
            'Va': (['As', 'In', 'Va'], 1000)
        }
        self.price = 5000
        # Market trade codes
        for trade_code in self.source_world.trade_codes:
            if trade_code in market_mods.keys():
                for code in market_mods[trade_code][0]:
                    LOGGER.debug('Checking source TC %s market TC %s',
                                 trade_code, code)
                    if code in self.market_world.trade_codes:
                        LOGGER.debug('Found match: adjustment = %s',
                                     market_mods[trade_code][1])
                        self.price += market_mods[trade_code][1]
                    LOGGER.debug('Price = Cr%s', self.price)
        # TL effect
        tl_mod = 0.1 * (int(self.source_world.tech_level) -
                        int(self.market_world.tech_level))
        LOGGER.debug('Price TL modifier = %s%%', int(100 * tl_mod))
        LOGGER.debug('Price TL change = %s', int(-self.price * tl_mod))
        self.price = int(self.price * (1 + tl_mod))
        if self.price < 0:
            LOGGER.debug('Price (Cr%s) < Cr0, resetting to Cr0', self.price)
            self.price = 0
        LOGGER.debug('Price = %s', self.price)
        self.actual_value = int(self.price * self.determine_actual_value())
        LOGGER.debug('actual value = %s', self.actual_value)
        self.commission = int(0.05 * self.broker_dm * self.price)
        LOGGER.debug('commission = %s', self.commission)
        self.net_actual_value = self.actual_value - self.commission

    def determine_actual_value(self, modifier=0):
        '''Determine actual value using flux roll'''
        broker_dm = int((self.broker_skill + 0.5) / 2)
        broker_dm = min(4, broker_dm)
        self.broker_dm = broker_dm
        flux = FLUX.roll() + modifier + self.broker_dm
        self.actual_value_rolls = (FLUX.die1, FLUX.die2)

        flux = max(-5, flux)
        flux = min(8, flux)

        if flux <= -4:
            actual_value_multiplier = float(9 + flux) / 10.0
        elif flux <= 3 and flux > -4:
            actual_value_multiplier = float(10 + flux) / 10.0
        elif flux <= 5 and flux > 3:
            actual_value_multiplier = float(7 + 2 * flux) / 10.0
        elif flux > 5:
            actual_value_multiplier = float(flux - 4) / 10.0
        LOGGER.debug('flux result = %s flux rolls = +%s -%s ', flux,
                     self.actual_value_rolls[0], self.actual_value_rolls[1])
        LOGGER.debug('modifier = %s actual_value_multiplier = %s', modifier,
                     actual_value_multiplier)

        return actual_value_multiplier

    def __str__(self):
        source = '{}-{} Cr{:,} {}'.format(
            self.source_world.tech_level,
            ' '.join(self.interesting_trade_codes), self.cost,
            self.description)
        return source

    def json(self):
        '''JSON representation'''
        if self.market_world is not None:
            market_world_trade_codes = self.market_world.trade_codes
            market_world_uwp = self.market_world.uwp()
        else:
            market_world_trade_codes = []
            market_world_uwp = None

        doc = {
            "cargo": str(self),
            "cost": self.cost,
            "description": self.description,
            "market": {
                "trade_codes": market_world_trade_codes,
                "uwp": market_world_uwp,
                "net_actual_value": self.net_actual_value,
                "gross_actual_value": self.actual_value,
                "broker_commission": self.commission
            },
            "price": self.price,
            "source": {
                "trade_codes": self.source_world.trade_codes,
                "uwp": self.source_world.uwp()
            },
            "tech_level": int(self.source_world.tech_level),
            "notes": {
                "actual_value_rolls": self.actual_value_rolls,
                "broker_skill": self.broker_skill
            }
        }
        return json.dumps(doc)

    @staticmethod
    def purge_ce_trade_codes(trade_codes):
        '''Purge CE trade codes'''
        try:
            assert isinstance(trade_codes, list)
        except AssertionError:
            raise ValueError('trade_codes must be type list')
        for indx, code in enumerate(trade_codes):
            if code in ['Ht', 'Lt']:
                del trade_codes[indx]
        return trade_codes