def initialize(self): self.__init() # add production lines as specified in db. if self.__auto_init: for prod_line, attributes in self.production_lines.iteritems(): if 'enabled_by_default' in attributes and not attributes[ 'enabled_by_default']: continue # It's set to false, don't add prod = self.create_production(prod_line) self.add_production(prod) # For newly built producers we set the utilisation to full for the first # few seconds, this avoids the low productivity icon being shown every # time a new producer is built temp_util = self.__utilisation self.__utilisation = FullUtilisation() Scheduler().add_new_object(Callback(self.__set_utilisation, temp_util), self, Scheduler().get_ticks(15))
def initialize(self): self.__init() # add production lines as specified in db. if self.__auto_init: for prod_line, attributes in self.production_lines.iteritems(): if 'enabled_by_default' in attributes and not attributes['enabled_by_default']: continue # It's set to false, don't add prod = self.create_production(prod_line) self.add_production(prod) # For newly built producers we set the utilisation to full for the first # few seconds, this avoids the low productivity icon being shown every # time a new producer is built temp_util = self.__utilisation self.__utilisation = FullUtilisation() Scheduler().add_new_object(Callback(self.__set_utilisation, temp_util), self, Scheduler().get_ticks(15))
class Producer(Component): """Class for objects, that produce something. @param auto_init: bool. If True, the producer automatically adds one production for each production_line. """ log = logging.getLogger("world.production") NAME = "producer" DEPENDENCIES = [StorageComponent] utilisation_mapping = { 'FieldUtilisation': FieldUtilisation, 'FullUtilisation': FullUtilisation } production_class = Production # INIT def __init__(self, auto_init=True, start_finished=False, productionlines=None, utilisation_calculator=None, is_mine_for=None, settler_upgrade_lines=None, **kwargs): """ @param productionline: yaml-dict for prod line data. Must not be changed since it is cached. @param utilisation_calculator: one of utilisatoin_mapping @param settler_upgrade_lines: data for settler upgrades. can one day be generalised to other upgrades """ if productionlines is None: productionlines = {} super(Producer, self).__init__(**kwargs) self.__auto_init = auto_init self.__start_finished = start_finished self.production_lines = productionlines assert utilisation_calculator is not None self.__utilisation = utilisation_calculator if settler_upgrade_lines: from horizons.world.building.settler import SettlerUpgradeData self.settler_upgrade_lines = SettlerUpgradeData(self, settler_upgrade_lines) self.production_lines = self.production_lines.copy() self.production_lines.update(self.settler_upgrade_lines.get_production_lines()) else: self.settler_upgrade_lines = None def __init(self): # we store productions in 2 dicts, one for the active ones, and one for the inactive ones. # the inactive ones won't get considered for needed_resources and such. # the production_line id is the key in the dict (=> a building must not have two identical # production lines) self._productions = {} self._inactive_productions = {} # Store whether or not the producer is active self.__active = True # Store whether or not the utilisation level is currently ok self.__utilisation_ok = True # BIG FAT NOTE: this has to be executed for all players for mp # even if this building has no status icons # TODO: think about whether this is enough gui-related so it belongs to the ExtScheduler, also check its performance when moving interval = Scheduler().get_ticks(3) run_in = self.session.random.randint(1, interval) # don't update all at once if self.instance.has_status_icon: Scheduler().add_new_object(self.update_capacity_utilisation, self, run_in=run_in, loops=-1, loop_interval = interval) def initialize(self): self.__init() # add production lines as specified in db. if self.__auto_init: for prod_line, attributes in self.production_lines.iteritems(): if 'enabled_by_default' in attributes and not attributes['enabled_by_default']: continue # It's set to false, don't add prod = self.create_production(prod_line) self.add_production(prod) # For newly built producers we set the utilisation to full for the first # few seconds, this avoids the low productivity icon being shown every # time a new producer is built temp_util = self.__utilisation self.__utilisation = FullUtilisation() Scheduler().add_new_object(Callback(self.__set_utilisation, temp_util), self, Scheduler().get_ticks(15)) def get_production_lines_by_level(self, level): prod_lines = [] for key, data in self.production_lines.iteritems(): if 'level' in data and level in data['level']: prod_lines.append(key) return prod_lines def create_production(self, id, load=False): """ @param id: production line id @param load: whether the production is used for loading. """ data = self.production_lines[id] production_class = self.production_class owner_inventory = self.instance._get_owner_inventory() # not really fancy way of selecting special production class if self.settler_upgrade_lines: if id == self.settler_upgrade_lines.get_production_line_id(self.instance.level+1): production_class = SingleUseProduction return production_class(inventory=self.instance.get_component(StorageComponent).inventory, owner_inventory=owner_inventory, prod_id=id, prod_data=data, load=load, start_finished=self.__start_finished) def add_production_by_id(self, production_line_id): """Convenience method. @param production_line_id: Production line from db """ production = self.create_production(production_line_id) self.add_production( production ) return production def update_capacity_utilisation(self): """Called by the scheduler to update the utilisation regularly""" if not self.capacity_utilisation_below(ProductivityLowStatus.threshold) is not self.__utilisation_ok: self.__utilisation_ok = not self.__utilisation_ok if self.__utilisation_ok: RemoveStatusIcon.broadcast(self, self.instance, ProductivityLowStatus) else: icon = ProductivityLowStatus(self.instance) AddStatusIcon.broadcast(self, icon) @property def capacity_utilisation(self): return self.__utilisation.capacity_utilisation(self) def capacity_utilisation_below(self, limit): return self.__utilisation.capacity_utilisation_below(limit, self) def load(self, db, worldid): # Call this before super, because we have to make sure this is called before the # ConcreteObject's callback which is added during loading Scheduler().add_new_object(self._on_production_change, self, run_in = 0) super(Producer, self).load(db, worldid) # load all productions self.__init() for line_id in db.get_production_lines_by_owner(worldid): production = self.create_production(line_id, load=True) assert isinstance(production, Production) production.load(db, worldid) self.add_production(production) self._update_decommissioned_icon() def save(self, db): super(Producer, self).save(db) for production in self.get_productions(): production.save(db, self.instance.worldid) # INTERFACE def add_production(self, production): assert isinstance(production, Production) self.log.debug('%s: added production line %s', self, production.get_production_line_id()) if production.is_paused(): self.log.debug('%s: added production line %s is paused', self, production.get_production_line_id()) self._inactive_productions[production.get_production_line_id()] = production else: self.log.debug('%s: added production line %s is active', self, production.get_production_line_id()) self._productions[production.get_production_line_id()] = production production.add_production_finished_listener(self._production_finished) # This would be called multiple times during init, just add it later this tick. # It also ensures that the changelistener would stick, we used to readd # the listener in load(), which was explained by this comment: # Listener has been removed in the productions.load(), because the # changelistener's load is called Scheduler().add_new_object( Callback(production.add_change_listener, self._on_production_change, call_listener_now=True), self, run_in=0 ) self.instance._changed() def _production_finished(self, production): """Gets called when a production finishes. Intercepts call, adds info and forwards it""" produced_resources = production.get_produced_resources() self.on_production_finished(produced_resources) def finish_production_now(self): """Cheat, makes current production finish right now (and produce the resources). Useful to make trees fully grown at game start.""" for production in self._productions.itervalues(): production.finish_production_now() def has_production_line(self, prod_line_id): """Checks if this instance has a production with a certain production line id""" return bool( self._get_production(prod_line_id) ) def remove_production(self, production): """Removes a production instance. @param production: Production instance""" production.remove() # production "destructor" if self.is_active(production): del self._productions[production.get_production_line_id()] # update decommissioned icon after removing production self._update_decommissioned_icon() self.instance._changed() self.on_activity_changed(self.is_active()) else: del self._inactive_productions[production.get_production_line_id()] def remove_production_by_id(self, prod_line_id): """ Convenience method. Assumes, that this production line id has been added to this instance. @param prod_line_id: production line id to remove """ self.remove_production( self._get_production(prod_line_id) ) def alter_production_time(self, modifier, prod_line_id=None): """Multiplies the original production time of all production lines by modifier @param modifier: a numeric value @param prod_line_id: id of production line to alter. None means every production line""" if prod_line_id is None: for production in self.get_productions(): production.alter_production_time(modifier) else: self._get_production(prod_line_id).alter_production_time(modifier) def remove(self): Scheduler().rem_all_classinst_calls(self) for production in self.get_productions(): self.remove_production(production) # call super() after removing all productions since it removes the instance (make it invalid) # which can be needed by changelisteners' actions (e.g. in remove_production method) super(Producer, self).remove() assert not self.get_productions() , 'Failed to remove %s ' % self.get_productions() # PROTECTED METHODS def _get_current_state(self): """Returns the current state of the producer. It is the most important state of all productions combined. Check the PRODUCTION.STATES constant for list of states and their importance.""" current_state = PRODUCTION.STATES.none for production in self.get_productions(): state = production.get_animating_state() if state is not None and current_state < state: current_state = state return current_state def get_productions(self): """Returns all productions, inactive and active ones, as list""" return self._productions.values() + self._inactive_productions.values() def get_production_lines(self): """Returns all production lines that have been added. @return: a list of prodline ids""" return self._productions.keys() + self._inactive_productions.keys() def _get_production(self, prod_line_id): """Returns a production of this producer by a production line id. @return: instance of Production or None""" if prod_line_id in self._productions: return self._productions[prod_line_id] elif prod_line_id in self._inactive_productions: return self._inactive_productions[prod_line_id] else: return None def is_active(self, production=None): """Checks if a production, or the at least one production if production is None, is active""" if production is None: for production in self.get_productions(): if not production.is_paused(): return True return False else: assert production.get_production_line_id() in self._productions or \ production.get_production_line_id() in self._inactive_productions return not production.is_paused() def set_active(self, production=None, active=True): """Pause or unpause a production (aka set it active/inactive). see also: is_active, toggle_active @param production: instance of Production. if None, we do it to all productions. @param active: whether to set it active or inactive""" if production is None: # set all for production in self.get_productions(): self.set_active(production, active) else: line_id = production.get_production_line_id() if active: if not self.is_active(production): self.log.debug("ResHandler %s: reactivating production %s", self.instance.worldid, line_id) self._productions[line_id] = production del self._inactive_productions[line_id] production.pause(pause=False) else: if self.is_active(production): self.log.debug("ResHandler %s: deactivating production %s", self.instance.worldid, line_id) self._inactive_productions[line_id] = production del self._productions[line_id] production.pause() self._update_decommissioned_icon() self.instance._changed() self.on_activity_changed(self.is_active()) def _update_decommissioned_icon(self): """Add or remove decommissioned icon.""" if not self.instance.has_status_icon: return if self.is_active() is not self.__active: self.__active = not self.__active if self.__active: RemoveStatusIcon.broadcast(self, self.instance, DecommissionedStatus) else: icon = DecommissionedStatus(self.instance) AddStatusIcon.broadcast(self, icon) def toggle_active(self, production=None): if production is None: for production in self.get_productions(): self.toggle_active(production) else: active = self.is_active(production) self.set_active(production, active = not active) def _on_production_change(self): """Makes the instance act according to the producers current state""" state = self._get_current_state() new_action = 'idle' if state is PRODUCTION.STATES.producing: new_action = "work" elif state is PRODUCTION.STATES.inventory_full: new_action = "idle_full" # don't force restarts as not to disturb sequences such as tree growth self.instance.act(new_action, repeating=True, force_restart=False) if self.instance.has_status_icon: full = state is PRODUCTION.STATES.inventory_full if full and not hasattr(self, "_producer_status_icon"): affected_res = set() # find them: for prod in self.get_productions(): affected_res = affected_res.union( prod.get_unstorable_produced_res() ) self._producer_status_icon = InventoryFullStatus(self.instance, affected_res) AddStatusIcon.broadcast(self, self._producer_status_icon) if not full and hasattr(self, "_producer_status_icon"): RemoveStatusIcon.broadcast(self, self.instance, InventoryFullStatus) del self._producer_status_icon def get_status_icons(self): l = super(Producer, self).get_status_icons() if self.capacity_utilisation_below(ProductivityLowStatus.threshold): l.append( ProductivityLowStatus() ) return l def __str__(self): return "Producer(owner: " + str(self.instance) + ")" def get_production_progress(self): """Returns the current progress of the active production.""" for production in self._productions.itervalues(): # Always return first production return production.progress for production in self._inactive_productions.itervalues(): # try inactive ones, if no active ones are found # this makes e.g. the boatbuilder's progress bar constant when you pause it return production.progress return 0 # No production available def __set_utilisation(self, utilisation): self.__utilisation = utilisation @classmethod def get_instance(cls, arguments=None): arguments = arguments and arguments.copy() or {} utilisation = None if 'utilisation' in arguments: if arguments['utilisation'] in cls.utilisation_mapping: utilisation = cls.utilisation_mapping[arguments['utilisation']]() del arguments['utilisation'] else: utilisation = Utilisation() if arguments.get('is_mine_for'): # this is more of an aspect than an actual subclass, but python doesn't allow # fast aspect-oriented programming cls = MineProducer return cls(utilisation_calculator=utilisation, **arguments)
class Producer(Component): """Class for objects, that produce something. @param auto_init: bool. If True, the producer automatically adds one production for each production_line. """ log = logging.getLogger("world.production") NAME = "producer" DEPENDENCIES = [StorageComponent] utilisation_mapping = { 'FieldUtilisation': FieldUtilisation, 'FullUtilisation': FullUtilisation } production_class = Production # INIT def __init__(self, auto_init=True, start_finished=False, productionlines=None, utilisation_calculator=None, is_mine_for=None, settler_upgrade_lines=None, **kwargs): """ @param productionline: yaml-dict for prod line data. Must not be changed since it is cached. @param utilisation_calculator: one of utilisatoin_mapping @param settler_upgrade_lines: data for settler upgrades. can one day be generalised to other upgrades """ if productionlines is None: productionlines = {} super(Producer, self).__init__(**kwargs) self.__auto_init = auto_init self.__start_finished = start_finished self.production_lines = productionlines assert utilisation_calculator is not None self.__utilisation = utilisation_calculator if settler_upgrade_lines: from horizons.world.building.settler import SettlerUpgradeData self.settler_upgrade_lines = SettlerUpgradeData( self, settler_upgrade_lines) self.production_lines = self.production_lines.copy() self.production_lines.update( self.settler_upgrade_lines.get_production_lines()) else: self.settler_upgrade_lines = None def __init(self): # we store productions in 2 dicts, one for the active ones, and one for the inactive ones. # the inactive ones won't get considered for needed_resources and such. # the production_line id is the key in the dict (=> a building must not have two identical # production lines) self._productions = {} self._inactive_productions = {} # Store whether or not the producer is active self.__active = True # Store whether or not the utilisation level is currently ok self.__utilisation_ok = True # BIG FAT NOTE: this has to be executed for all players for mp # even if this building has no status icons # TODO: think about whether this is enough gui-related so it belongs to the ExtScheduler # also check its performance when moving interval = Scheduler().get_ticks(3) run_in = self.session.random.randint( 1, interval) # don't update all at once if self.instance.has_status_icon: Scheduler().add_new_object(self.update_capacity_utilisation, self, run_in=run_in, loops=-1, loop_interval=interval) def initialize(self): self.__init() # add production lines as specified in db. if self.__auto_init: for prod_line, attributes in self.production_lines.iteritems(): if 'enabled_by_default' in attributes and not attributes[ 'enabled_by_default']: continue # It's set to false, don't add prod = self.create_production(prod_line) self.add_production(prod) # For newly built producers we set the utilisation to full for the first # few seconds, this avoids the low productivity icon being shown every # time a new producer is built temp_util = self.__utilisation self.__utilisation = FullUtilisation() Scheduler().add_new_object(Callback(self.__set_utilisation, temp_util), self, Scheduler().get_ticks(15)) def get_production_lines_by_level(self, level): prod_lines = [] for key, data in self.production_lines.iteritems(): if 'level' in data and level in data['level']: prod_lines.append(key) return prod_lines def create_production(self, id, load=False): """ @param id: production line id @param load: whether the production is used for loading. """ data = self.production_lines[id] production_class = self.production_class owner_inventory = self.instance._get_owner_inventory() # not really fancy way of selecting special production class if self.settler_upgrade_lines: if id == self.settler_upgrade_lines.get_production_line_id( self.instance.level + 1): production_class = SingleUseProduction return production_class( inventory=self.instance.get_component(StorageComponent).inventory, owner_inventory=owner_inventory, prod_id=id, prod_data=data, load=load, start_finished=self.__start_finished) def add_production_by_id(self, production_line_id): """Convenience method. @param production_line_id: Production line from db """ production = self.create_production(production_line_id) self.add_production(production) return production def update_capacity_utilisation(self): """Called by the scheduler to update the utilisation regularly""" if not self.capacity_utilisation_below( ProductivityLowStatus.threshold) is not self.__utilisation_ok: self.__utilisation_ok = not self.__utilisation_ok if self.__utilisation_ok: RemoveStatusIcon.broadcast(self, self.instance, ProductivityLowStatus) else: icon = ProductivityLowStatus(self.instance) AddStatusIcon.broadcast(self, icon) @property def capacity_utilisation(self): return self.__utilisation.capacity_utilisation(self) def capacity_utilisation_below(self, limit): return self.__utilisation.capacity_utilisation_below(limit, self) def load(self, db, worldid): # Call this before super, because we have to make sure this is called before the # ConcreteObject's callback which is added during loading Scheduler().add_new_object(self._on_production_change, self, run_in=0) super(Producer, self).load(db, worldid) # load all productions self.__init() for line_id in db.get_production_lines_by_owner(worldid): production = self.create_production(line_id, load=True) assert isinstance(production, Production) production.load(db, worldid) self.add_production(production) self._update_decommissioned_icon() def save(self, db): super(Producer, self).save(db) for production in self.get_productions(): production.save(db, self.instance.worldid) # INTERFACE def add_production(self, production): assert isinstance(production, Production) self.log.debug('%s: added production line %s', self, production.get_production_line_id()) if production.is_paused(): self.log.debug('%s: added production line %s is paused', self, production.get_production_line_id()) self._inactive_productions[ production.get_production_line_id()] = production else: self.log.debug('%s: added production line %s is active', self, production.get_production_line_id()) self._productions[production.get_production_line_id()] = production production.add_production_finished_listener(self._production_finished) # This would be called multiple times during init, just add it later this tick. # It also ensures that the changelistener would stick, we used to readd # the listener in load(), which was explained by this comment: # Listener has been removed in the productions.load(), because the # changelistener's load is called Scheduler().add_new_object(Callback(production.add_change_listener, self._on_production_change, call_listener_now=True), self, run_in=0) self.instance._changed() def _production_finished(self, production): """Gets called when a production finishes. Intercepts call, adds info and forwards it""" produced_resources = production.get_produced_resources() self.on_production_finished(produced_resources) def finish_production_now(self): """Cheat, makes current production finish right now (and produce the resources). Useful to make trees fully grown at game start.""" for production in self._productions.itervalues(): production.finish_production_now() def has_production_line(self, prod_line_id): """Checks if this instance has a production with a certain production line id""" return bool(self._get_production(prod_line_id)) def remove_production(self, production): """Removes a production instance. @param production: Production instance""" production.remove() # production "destructor" if self.is_active(production): del self._productions[production.get_production_line_id()] # update decommissioned icon after removing production self._update_decommissioned_icon() self.instance._changed() self.on_activity_changed(self.is_active()) else: del self._inactive_productions[production.get_production_line_id()] def remove_production_by_id(self, prod_line_id): """ Convenience method. Assumes, that this production line id has been added to this instance. @param prod_line_id: production line id to remove """ self.remove_production(self._get_production(prod_line_id)) def alter_production_time(self, modifier, prod_line_id=None): """Multiplies the original production time of all production lines by modifier @param modifier: a numeric value @param prod_line_id: id of production line to alter. None means every production line""" if prod_line_id is None: for production in self.get_productions(): production.alter_production_time(modifier) else: self._get_production(prod_line_id).alter_production_time(modifier) def remove(self): Scheduler().rem_all_classinst_calls(self) for production in self.get_productions(): self.remove_production(production) # call super() after removing all productions since it removes the instance (make it invalid) # which can be needed by changelisteners' actions (e.g. in remove_production method) super(Producer, self).remove() assert not self.get_productions( ), 'Failed to remove %s ' % self.get_productions() # PROTECTED METHODS def _get_current_state(self): """Returns the current state of the producer. It is the most important state of all productions combined. Check the PRODUCTION.STATES constant for list of states and their importance.""" current_state = PRODUCTION.STATES.none for production in self.get_productions(): state = production.get_animating_state() if state is not None and current_state < state: current_state = state return current_state def get_productions(self): """Returns all productions, inactive and active ones, as list""" return self._productions.values() + self._inactive_productions.values() def get_production_lines(self): """Returns all production lines that have been added. @return: a list of prodline ids""" return self._productions.keys() + self._inactive_productions.keys() def _get_production(self, prod_line_id): """Returns a production of this producer by a production line id. @return: instance of Production or None""" if prod_line_id in self._productions: return self._productions[prod_line_id] elif prod_line_id in self._inactive_productions: return self._inactive_productions[prod_line_id] else: return None def is_active(self, production=None): """Checks if a production, or the at least one production if production is None, is active""" if production is None: for production in self.get_productions(): if not production.is_paused(): return True return False else: assert production.get_production_line_id() in self._productions or \ production.get_production_line_id() in self._inactive_productions return not production.is_paused() def set_active(self, production=None, active=True): """Pause or unpause a production (aka set it active/inactive). see also: is_active, toggle_active @param production: instance of Production. if None, we do it to all productions. @param active: whether to set it active or inactive""" if production is None: # set all for production in self.get_productions(): self.set_active(production, active) else: line_id = production.get_production_line_id() if active: if not self.is_active(production): self.log.debug("ResHandler %s: reactivating production %s", self.instance.worldid, line_id) self._productions[line_id] = production del self._inactive_productions[line_id] production.pause(pause=False) else: if self.is_active(production): self.log.debug("ResHandler %s: deactivating production %s", self.instance.worldid, line_id) self._inactive_productions[line_id] = production del self._productions[line_id] production.pause() self._update_decommissioned_icon() self.instance._changed() self.on_activity_changed(self.is_active()) def _update_decommissioned_icon(self): """Add or remove decommissioned icon.""" if not self.instance.has_status_icon: return if self.is_active() is not self.__active: self.__active = not self.__active if self.__active: RemoveStatusIcon.broadcast(self, self.instance, DecommissionedStatus) else: icon = DecommissionedStatus(self.instance) AddStatusIcon.broadcast(self, icon) def toggle_active(self, production=None): if production is None: for production in self.get_productions(): self.toggle_active(production) else: active = self.is_active(production) self.set_active(production, active=not active) def _on_production_change(self): """Makes the instance act according to the producers current state""" state = self._get_current_state() new_action = 'idle' if state is PRODUCTION.STATES.producing: new_action = "work" elif state is PRODUCTION.STATES.inventory_full: new_action = "idle_full" # don't force restarts as not to disturb sequences such as tree growth self.instance.act(new_action, repeating=True, force_restart=False) if self.instance.has_status_icon: full = state is PRODUCTION.STATES.inventory_full if full and not hasattr(self, "_producer_status_icon"): affected_res = set() # find them: for prod in self.get_productions(): affected_res = affected_res.union( prod.get_unstorable_produced_res()) self._producer_status_icon = InventoryFullStatus( self.instance, affected_res) AddStatusIcon.broadcast(self, self._producer_status_icon) if not full and hasattr(self, "_producer_status_icon"): RemoveStatusIcon.broadcast(self, self.instance, InventoryFullStatus) del self._producer_status_icon def get_status_icons(self): l = super(Producer, self).get_status_icons() if self.capacity_utilisation_below(ProductivityLowStatus.threshold): l.append(ProductivityLowStatus()) return l def __str__(self): return "Producer(owner: " + str(self.instance) + ")" def get_production_progress(self): """Returns the current progress of the active production.""" for production in self._productions.itervalues(): # Always return first production return production.progress for production in self._inactive_productions.itervalues(): # try inactive ones, if no active ones are found # this makes e.g. the boatbuilder's progress bar constant when you pause it return production.progress return 0 # No production available def __set_utilisation(self, utilisation): self.__utilisation = utilisation @classmethod def get_instance(cls, arguments=None): arguments = arguments and arguments.copy() or {} utilisation = None if 'utilisation' in arguments: if arguments['utilisation'] in cls.utilisation_mapping: utilisation = cls.utilisation_mapping[ arguments['utilisation']]() del arguments['utilisation'] else: utilisation = Utilisation() if arguments.get('is_mine_for'): # this is more of an aspect than an actual subclass, but python doesn't allow # fast aspect-oriented programming cls = MineProducer return cls(utilisation_calculator=utilisation, **arguments)