Package horizons :: Package world :: Module ingametype
[hide private]
[frames] | no frames]

Source Code for Module horizons.world.ingametype

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22  import hashlib 
 23  import importlib 
 24   
 25  from horizons.constants import TIER 
 26  from horizons.i18n import gettext_lazy as LazyT 
27 28 29 -class IngameType(type):
30 """Class that is used to create Ingame-Type-Classes from yaml data. 31 @param id: building or unit type id 32 @param yaml_data: a dict containing all the data read from yaml files 33 34 Note this creates class types, NOT instances. 35 These types are created at the beginning of a session 36 and are later used to create instances, when buildings are built. 37 The __new__() function uses quite some python magic to construct the new class. 38 39 TUTORIAL: 40 Check out the __new__() function if you feel you're pretty good with python and 41 are interested in how it all works. Otherwise, continue to the __init__() function. 42 """ 43 44 # Base package to import from, must end with the '.', the package is appended 45 basepackage = 'horizons.world.building.' 46 # Class name for the type.__new__ constructor 47 classstring = 'Type[{id}]' 48
49 - def __new__(self, id, yaml_data):
50 class_package, class_name = yaml_data['baseclass'].split('.', 1) 51 52 @classmethod 53 def load(cls, session, db, worldid): 54 self = cls.__new__(cls) 55 self.session = session 56 super(cls, self).load(db, worldid) 57 return self
58 59 module = importlib.import_module(str(self.basepackage + class_package)) 60 return type.__new__(self, self.classstring.format(id=id), 61 (getattr(module, class_name),), 62 {'load': load, 'class_package': str(class_package), 'class_name': str(class_name)})
63
64 - def _strip_translation_marks(self, string):
65 """Converts object translation `string` to translated object for in-game display. 66 67 Object properties supposed to be translated are recognized by the 68 (subsequently stripped) leading `_ `. 69 If `string` is supposed to be translated, returns lazy translation object of `string`. 70 If `string` is not supposed to be translated, returns `string` unchanged. 71 If `string` is None (not defined or empty in yaml), returns empty unicode. 72 """ 73 if not string: 74 return '' 75 if string.startswith("_ "): 76 return LazyT(string[2:]) 77 else: 78 return string
79
80 - def __init__(self, id, yaml_data):
81 self.id = id 82 # self._name is always some default name 83 # self._level_specific_names is optional and contains a dict like this: { level_id : name } 84 # (with entries for all tiers in which it is active) 85 name_data = yaml_data['name'] 86 start_tier = yaml_data.get('tier', TIER.NATURE) # first tier where object is available 87 if isinstance(name_data, dict): # { level_id : name } 88 # fill up dict (fall down to highest tier which has a name specified 89 self._level_specific_names = {} 90 for lvl in range(start_tier, TIER.CURRENT_MAX + 1): 91 name = name_data.get(lvl) 92 if name is None: 93 name = self._level_specific_names.get(lvl - 1) 94 assert name is not None, ( 95 "Error in object file:\n" 96 "'name' attribute needs to at least describe tier {}. " 97 "Found:\n{}").format(name_data, start_tier) 98 self._level_specific_names[lvl] = name 99 else: 100 self._level_specific_names[lvl] = self._strip_translation_marks(name) 101 102 self._name = self._level_specific_names[start_tier] # default name: lowest available 103 else: # assume just one string 104 self._name = self._strip_translation_marks(name_data) 105 self.radius = yaml_data['radius'] 106 self.component_templates = yaml_data['components'] 107 self.action_sets = yaml_data['actionsets'] 108 self.baseclass = yaml_data['baseclass'] # mostly only for debug 109 self._real_object = None # wrapped by _fife_object 110 111 self._parse_component_templates() 112 113 # TODO: move this to the producer component as soon as there is support for class attributes there 114 self.additional_provided_resources = yaml_data.get('additional_provided_resources', []) 115 116 """TUTORIAL: Now you know the basic attributes each type has. 117 Further attributes specific to buildings and units can be found in 118 horizons/world/{building,units}/__init__.py 119 which contains the unit and building specific attributes and loading. 120 121 By now you should know the basic constructs used in UH, so we feel 122 comfortable stopping the tutorial here. Be sure to join our IRC 123 channel and idle around there, ask us if you're stuck and so on. 124 125 You'll find tasks for getting into the code in our issue tracker at 126 https://github.com/unknown-horizons/unknown-horizons/issues 127 Especially look for issues with the 'starter' label attached! 128 129 Other relevant parts of the code you might be interested in are: 130 * commands: horizons/commands. Abstracts all user interactions. 131 * scheduler: horizons/scheduler.py. Manages ingame time. 132 * extscheduler: horizons/extscheduler.py. Manages wall clock time. 133 * scenario: horizons/scenario. Condition-action system for scenarios 134 * automatic tests: tests/. Contains unit tests, gui tests and game (system) tests 135 * networking: horizons/network. Sending stuff over the wire 136 * concreteobject: horizons/world/concreteobject.py. Things with graphical representations 137 * gui: horizons/gui. The ugly parts. IngameGui and Gui, tabs and widgets. 138 * production: horizons/world/production 139 ** Producer: producer component, manages everything 140 ** ProductionLine: keeps data about the different production lines. 141 ** Production: the alive version of the production line. Used when a building 142 actually produces something, stores progress and the like. 143 * engine: horizons/engine. Direct interface to fife. 144 * ai: horizons/ai/aiplayer. Way too big to describe here. 145 """
146
147 - def _parse_component_templates(self):
148 """Prepares misc data in self.component_templates""" 149 producer = [comp for comp in self.component_templates if 150 isinstance(comp, dict) and next(iter(comp.keys())) == 'ProducerComponent'] 151 if producer: 152 # we want to support string production line ids, the code should still only see integers 153 # therefore we do a deterministic string -> int conversion here 154 155 producer_data = producer[0]['ProducerComponent'] 156 original_data = producer_data['productionlines'] 157 158 new_data = {} 159 160 for old_key, v in original_data.items(): 161 if isinstance(old_key, int): 162 new_key = old_key 163 else: 164 # hash the string 165 new_key = int(hashlib.sha1(old_key.encode('utf-8')).hexdigest(), 16) 166 # crop to integer. this might not be necessary, however the legacy code operated 167 # on this data type, so problems might occur, also with respect to performance. 168 # in principle, strings and longs should also be supported, but for the sake of 169 # safety, we use ints. 170 new_key = int(new_key % 2**31) # this ensures it's an integer on all reasonable platforms 171 if new_key in new_data: 172 raise Exception('Error: production line id conflict.' 173 ' Please change "{}" to anything else for "{}"' 174 .format(old_key, self.name)) 175 new_data[new_key] = v 176 177 producer_data['productionlines'] = new_data
178 179 @property
180 - def _fife_object(self):
181 if self._real_object is None: 182 self._loadObject() 183 return self._real_object
184
185 - def _loadObject(self):
186 """Inits self._real_object""" 187 raise NotImplementedError
188 189 @property
190 - def name(self):
191 return self._name
192