Source code for PyPoE.poe.sim.mods

"""
Overview
===============================================================================

+----------+------------------------------------------------------------------+
| Path     | PyPoE/poe/sim/mods.py                                            |
+----------+------------------------------------------------------------------+
| Version  | 1.0.0a0                                                          |
+----------+------------------------------------------------------------------+
| Revision | $Id: a887c5ad480fe2a81026962eaafbb642e48d967a $                  |
+----------+------------------------------------------------------------------+
| Author   | Omega_K2                                                         |
+----------+------------------------------------------------------------------+

Description
===============================================================================

Utilities for dealing with Mods.dat.

This module implements some generic functions to simplify dealing with Mods.dat
and perform several common tasks.

.. warning::
    This module is intended to be used with
    :class:`PyPoE.poe.file.dat.RelationalReader`.

Agreement
===============================================================================

See PyPoE/LICENSE

Documentation
===============================================================================

.. autoclass:: SpawnChanceCalculator
    :special-members: __init__

.. autofunction:: get_translation
.. autofunction:: get_translation_file_from_domain
.. autofunction:: get_mod_from_id
.. autofunction:: get_spawn_weight
.. autofunction:: generate_spawnable_mod_list

"""

# =============================================================================
# Imports
# =============================================================================

# Python

# 3rd-party

# self
from PyPoE.poe.file.dat import DatRecord
from PyPoE.poe.file.translations import TranslationFileCache
from PyPoE.poe.constants import MOD_DOMAIN, MOD_GENERATION_TYPE, MOD_STATS_RANGE

# =============================================================================
# Globals
# =============================================================================

__all__ = [
    'SpawnChanceCalculator',
    'generate_spawnable_mod_list',
    'get_mod_from_id',
    'get_spawn_weight',
    'get_translation',
    'get_translation_file_from_domain',
]

_translation_map = {
    MOD_DOMAIN.MONSTER: 'monster_stat_descriptions.txt',
    MOD_DOMAIN.CHEST: 'chest_stat_descriptions.txt',
    MOD_DOMAIN.AREA: 'map_stat_descriptions.txt',
    MOD_DOMAIN.ATLAS: 'atlas_stat_descriptions.txt',
    MOD_DOMAIN.LEAGUESTONE: 'leaguestone_stat_descriptions.txt',
    MOD_DOMAIN.DELVE_AREA: 'map_stat_descriptions.txt',
}

# =============================================================================
# Classes
# =============================================================================


[docs]class SpawnChanceCalculator(object): """ Class to calculate spawn chances. """
[docs] def __init__(self, mod_list, tags): """ Parameters ---------- mod_list : list[DatRecord] The mod list to base the calculations on tags : list[str] List of tag identifiers """ self.mod_list = mod_list self.tags = tags self.total_spawn_weight = self.get_total_spawn_weight()
[docs] def get_total_spawn_weight(self): """ Calculate the total spawn weight based on the stored modifier list Returns ------- int Sum of spawn weights """ total_spawn_weight = 0 for mod in self.mod_list: total_spawn_weight += self.get_spawn_weight(mod) return total_spawn_weight
[docs] def get_mod(self, mod_id): """ Returns the mod for the specified mod id based on the stored modifier list or None if it isn't found. Parameters ---------- mod_id : str The mod identifier to look for Returns ------- DatRecord or None Returns the mod if found, None otherwise See Also -------- :func:`get_mod_from_id` """ return get_mod_from_id(mod_id, self.mod_list)
[docs] def get_spawn_weight(self, mod): """ Calculates the spawn weight of the given mod based on the stored list of tags. Parameters ---------- mod : DatRecord The mod to calculate the spawn weight for. tags : list[str] List of applicable tag identifiers. Returns ------- int Calculated spawn weight See Also -------- :func:`get_spawn_weight` """ return get_spawn_weight(mod, self.tags)
[docs] def spawn_chance(self, mod_or_id, remove=True): """ Calculates the spawn chance for the given mod based on the tags and the mod list given to this instance. If remove is specified the mod is also removed from the mod list. It is recommended to leave this option enabled, as it will take care of updating tags and mods of the same grouping, which will guarantee that future mods rolled on this instance will be calculated properly. Parameters ---------- mod_or_id : str | DatRecord Id of the mod or the instance of the mod row remove : bool Remove the mod from the list once the chance has been calculated Returns ------- float The calculated spawn chance for the mod Raises ------ TypeError if mod_or_id has an invalid type ValueError if mod_or_id not found """ if isinstance(mod_or_id, str): mod = self.get_mod(mod_or_id) if mod is None: return 0 elif isinstance(mod_or_id, DatRecord): mod = mod_or_id else: raise TypeError( 'mod_or_id is of invalid type "%s"' % type(mod_or_id) ) weight = self.get_spawn_weight(mod) chance = weight/self.total_spawn_weight if remove: mods = [] for m in self.mod_list: if m['CorrectGroup'] == mod['CorrectGroup']: continue mods.append(m) self.mod_list = mods for tag in mod['TagsKeys']: self.tags.append(tag['Id']) self.total_spawn_weight = self.get_total_spawn_weight() return chance
# ============================================================================= # Functions # =============================================================================
[docs]def get_translation_file_from_domain(domain): """ Returns the likely stat translation file for a given mod domain. Parameters ---------- domain : int Id of the domain Returns ------- str name of the stat translation file """ try: return _translation_map[domain] except KeyError: return 'stat_descriptions.txt'
[docs]def get_translation(mod, translation_cache, translation_file=None, **kwargs): """ Returns the Translation result of the stats found on the specified mod using the specified TranslationFileCache. Parameters ---------- mod : DatRecord translation_cache : TranslationFileCache :class:`PyPoE.poe.file.TranslationCache` instance to retrieve the translation file from. translation_file : str Name of the translation file to use. If left empty, it will be automatically determined based on the mod domain. Returns ------- TranslationResult """ stats = [] for i in MOD_STATS_RANGE: stat = mod['StatsKey%s' % i] if stat: stats.append(stat) ids = [] values = [] for i, stat in enumerate(stats): j = i + 1 values.append([mod['Stat%sMin' % j], mod['Stat%sMax' % j]]) ids.append(stat['Id']) if translation_file is None: tf_name = get_translation_file_from_domain(mod['Domain']) else: tf_name = translation_file return translation_cache[tf_name].get_translation( ids, values, full_result=True, **kwargs )
[docs]def get_mod_from_id(mod_id, mod_list): """ Returns the mod for given mod or None if it isn't found. Parameters ---------- mod_id : str The mod identifier to look for mod_list : list[DatRecord] List of mods to search in (or dat file) Returns ------- DatRecord or None Returns the mod if found, None otherwise """ for mod in mod_list: if mod['Id'] == mod_id: return mod return None
[docs]def get_spawn_weight(mod, tags): """ Calculates the spawn weight of the given mod for the given tags. Parameters ---------- mod : DatRecord The mod to calculate the spawn weight for. tags : list[str] List of applicable tag identifiers. Returns ------- int Calculated spawn weight """ current_weight = 0 for i, tag in enumerate(mod['SpawnWeight_TagsKeys']): weight = mod['SpawnWeight_Values'][i] if tag['Id'] in tags: current_weight = weight break return current_weight
[docs]def generate_spawnable_mod_list( mod_dat_file, domain, generation_type, level=1, tags=['default', ] ): """ Generates a list of modifiers that can be spawned for the specified parameters, i.e. mods that can not spawn will be removed. TODO: Certain generation types/domains may have different rules. Parameters ---------- mod_dat_file : `DatFile` domain : MOD_DOMAIN The mod domain generation_type : MOD_GENERATION_TYPE The mod generation type level : int The level of object to the mod would be spawned on tags : list[str] List of tags for this object Returns ------- list[DatRecord] Returns a list of applicable mod rows that have a spawn weighting above 0. Raises ------ TypeError if domain is not a valid MOD_DOMAIN constant if generation_type is not a valid MOD_GENERATION_TYPE constant """ if not isinstance(domain, MOD_DOMAIN): raise TypeError('domain must be a MOD_DOMAIN instance.') if not isinstance(generation_type, MOD_GENERATION_TYPE): raise TypeError( 'generation_type must be a MOD_GENERATION_TYPE instance.' ) mods = [] for mod in mod_dat_file: if level < mod['Level']: continue if mod['Domain'] != domain: continue if mod['GenerationType'] != generation_type: continue # Mod can't spawn if get_spawn_weight(mod, tags) == 0: continue mods.append(mod) return mods