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 utilization 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.__utilization self.__utilization = FullUtilization() Scheduler().add_new_object(Callback(self.__set_utilization, 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 utilization 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.__utilization self.__utilization = FullUtilization() Scheduler().add_new_object(Callback(self.__set_utilization, 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] utilization_mapping = { 'FieldUtilization': FieldUtilization, 'FullUtilization': FullUtilization } production_class = Production # INIT def __init__(self, auto_init=True, start_finished=False, productionlines=None, utilization_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 utilization_calculator: one of utilization_mapping @param settler_upgrade_lines: data for settler upgrades. can one day be generalized 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 utilization_calculator is not None self.__utilization = utilization_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 utilization level is currently ok self.__utilization_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_utilization, 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 utilization 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.__utilization self.__utilization = FullUtilization() Scheduler().add_new_object(Callback(self.__set_utilization, 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_utilization(self): """Called by the scheduler to update the utilization regularly""" if not self.capacity_utilization_below(ProductivityLowStatus.threshold) is not self.__utilization_ok: self.__utilization_ok = not self.__utilization_ok if self.__utilization_ok: RemoveStatusIcon.broadcast(self, self.instance, ProductivityLowStatus) else: icon = ProductivityLowStatus(self.instance) AddStatusIcon.broadcast(self, icon) @property def capacity_utilization(self): return self.__utilization.capacity_utilization(self) def capacity_utilization_below(self, limit): return self.__utilization.capacity_utilization_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_utilization_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_utilization(self, utilization): self.__utilization = utilization @classmethod def get_instance(cls, arguments=None): arguments = arguments and arguments.copy() or {} utilization = None if 'utilization' in arguments: if arguments['utilization'] in cls.utilization_mapping: utilization = cls.utilization_mapping[arguments['utilization']]() del arguments['utilization'] else: utilization = Utilization() 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(utilization_calculator=utilization, **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] utilization_mapping = { 'FieldUtilization': FieldUtilization, 'FullUtilization': FullUtilization } produces_resource = True production_class = Production # INIT def __init__(self, auto_init=True, start_finished=False, productionlines=None, utilization_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 utilization_calculator: one of utilization_mapping @param settler_upgrade_lines: data for settler upgrades. can one day be generalized 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 utilization_calculator is not None self.__utilization = utilization_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 utilization level is currently ok self.__utilization_ok = True # Track if the producer is being removed self.__removal_started = False # 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_utilization, 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.items(): 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 utilization 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.__utilization self.__utilization = FullUtilization() Scheduler().add_new_object(Callback(self.__set_utilization, temp_util), self, Scheduler().get_ticks(15)) def get_production_lines_by_level(self, level): prod_lines = [] for key, data in self.production_lines.items(): 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 create_production_line(self, id): """Creates a production line instance, this is meant only for data transfer and READONLY use! If you want to use production lines for anything else, go the proper way of the production class.""" assert id in self.production_lines data = self.production_lines[id] return ProductionLine(id, data) 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_utilization(self): """Called by the scheduler to update the utilization regularly""" if not self.capacity_utilization_below( ProductivityLowStatus.threshold) is not self.__utilization_ok: self.__utilization_ok = not self.__utilization_ok if self.__utilization_ok: RemoveStatusIcon.broadcast(self, self.instance, ProductivityLowStatus) else: self._add_status_icon(ProductivityLowStatus(self.instance)) @property def capacity_utilization(self): return self.__utilization.capacity_utilization(self) def capacity_utilization_below(self, limit): return self.__utilization.capacity_utilization_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.values(): 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): self.__removal_started = True 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 {} '.format( 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 list(self._productions.values()) + list( self._inactive_productions.values()) def get_production_lines(self): """Returns all production lines that have been added. @return: a list of prodline ids""" return list(self._productions.keys()) + list( 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 _add_status_icon(self, icon): if not self.__removal_started: AddStatusIcon.broadcast(self, icon) 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: self._add_status_icon(DecommissionedStatus(self.instance)) 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) self._add_status_icon(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_utilization_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.values(): # Always return first production return production.progress for production in self._inactive_productions.values(): # 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_utilization(self, utilization): self.__utilization = utilization @classmethod def get_instance(cls, arguments=None): arguments = arguments and arguments.copy() or {} utilization = None if 'utilization' in arguments: if arguments['utilization'] in cls.utilization_mapping: utilization = cls.utilization_mapping[ arguments['utilization']]() del arguments['utilization'] else: utilization = Utilization() 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(utilization_calculator=utilization, **arguments)