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

Source Code for Module horizons.world.resourcehandler

  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  from copy import copy 
 23   
 24  from horizons.component.ambientsoundcomponent import AmbientSoundComponent 
 25  from horizons.component.storagecomponent import StorageComponent 
 26  from horizons.constants import PRODUCTION 
 27  from horizons.gui.tabs import InventoryTab, ProductionOverviewTab 
 28  from horizons.util.worldobject import WorldObject 
 29  from horizons.world.production.producer import Producer 
 30   
 31   
32 -class ResourceTransferHandler:
33 """Objects that can transfer resources. ResourceHandler and units with storages"""
34 - def transfer_to_storageholder(self, amount, res_id, transfer_to, signal_errors=False):
35 """Transfers amount of res_id to transfer_to. 36 @param transfer_to: worldid or object reference 37 @param signal_errors: whether to play an error sound in case the transfer completely failed (no res transferred) 38 @return: amount that was actually transferred (NOTE: this is different from the 39 return value of inventory.alter, since here are 2 storages involved) 40 """ 41 try: 42 transfer_to = WorldObject.get_object_by_id(int(transfer_to)) 43 except TypeError: # transfer_to not an int, assume already obj 44 pass 45 # take res from self 46 ret = self.get_component(StorageComponent).inventory.alter(res_id, -amount) 47 # check if we were able to get the planned amount 48 ret = amount if amount < abs(ret) else abs(ret) 49 # put res to transfer_to 50 ret = transfer_to.get_component(StorageComponent).inventory.alter(res_id, amount - ret) 51 self.get_component(StorageComponent).inventory.alter(res_id, ret) # return resources that did not fit 52 actually_transfered = amount - ret 53 if signal_errors and actually_transfered == 0: 54 AmbientSoundComponent.play_special('error') 55 return actually_transfered
56 57
58 -class ResourceHandler(ResourceTransferHandler):
59 """The ResourceHandler class acts as a basic class for describing objects 60 that handle resources. This means the objects can provide resources for 61 Collectors and have multiple productions. This is a base class, meaning 62 you have to override a lot of functions in subclasses before you can actually 63 use it. You can maybe understand our idea about the ResourceHandler if you 64 look at the uml digramm: development/uml/production_classes.png 65 66 A ResourceHandler must not have more than 1 production with the same prod line id. 67 """ 68 tabs = (ProductionOverviewTab, InventoryTab) 69 70 ## INIT/DESTRUCT
71 - def __init__(self, **kwargs):
72 super(ResourceHandler, self).__init__(**kwargs)
73
74 - def __init(self):
75 # list of collectors that are on the way here 76 self.__incoming_collectors = [] 77 self.provided_resources = self._load_provided_resources()
78
79 - def initialize(self):
80 super(ResourceHandler, self).initialize() 81 self.__init()
82
83 - def save(self, db):
84 super(ResourceHandler, self).save(db)
85
86 - def load(self, db, worldid):
87 super(ResourceHandler, self).load(db, worldid) 88 self.__init()
89
90 - def remove(self):
91 super(ResourceHandler, self).remove() 92 while self.__incoming_collectors: # safe list remove here 93 self.__incoming_collectors[0].cancel()
94 95 ## INTERFACE
96 - def get_consumed_resources(self, include_inactive=False):
97 """Returns the needed resources that are used by the productions 98 currently active. *include_inactive* will also include resources 99 used in a production line that is currently inactive.""" 100 needed_res = set() 101 if self.has_component(Producer): 102 prod_comp = self.get_component(Producer) 103 productions = copy(prod_comp._productions) 104 if include_inactive: 105 productions.update(prod_comp._inactive_productions) 106 for production in productions.values(): 107 needed_res.update(production.get_consumed_resources().keys()) 108 return list(needed_res)
109
110 - def get_produced_resources(self):
111 """Returns the resources, that are produced by productions, that are currently active""" 112 produced_resources = set() 113 if self.has_component(Producer): 114 prod_comp = self.get_component(Producer) 115 for production in prod_comp._productions.values(): 116 produced_resources.update(production.get_produced_resources().keys()) 117 return list(produced_resources)
118
120 """Returns provided resources, where at least 1 ton is available""" 121 return [res for res in self.provided_resources if self.get_component(StorageComponent).inventory[res] > 0]
122
124 """Returns a list of resources, that are currently consumed in a production.""" 125 consumed_res = set() 126 if self.has_component(Producer): 127 prod_comp = self.get_component(Producer) 128 for production in prod_comp._productions.values(): 129 if production.get_state() == PRODUCTION.STATES.producing: 130 consumed_res.update(production.get_consumed_resources().keys()) 131 return list(consumed_res)
132
134 """Needed, but not currently consumed resources. 135 Opposite of get_currently_consumed_resources.""" 136 # use set types since they support the proper operation 137 currently_consumed = frozenset(self.get_currently_consumed_resources()) 138 consumed = frozenset(self.get_consumed_resources()) 139 return list(consumed - currently_consumed)
140
141 - def get_needed_resources(self):
142 """Returns list of resources, where free space in the inventory exists.""" 143 return [res for res in self.get_consumed_resources() 144 if self.get_component(StorageComponent).inventory.get_free_space_for(res) > 0]
145
146 - def add_incoming_collector(self, collector):
147 assert collector not in self.__incoming_collectors 148 self.__incoming_collectors.append(collector)
149
150 - def remove_incoming_collector(self, collector):
151 self.__incoming_collectors.remove(collector)
152
153 - def _get_owner_inventory(self):
154 """Returns the inventory of the owner to be able to retrieve special resources such as gold. 155 The production system should be as decoupled as possible from actual world objects, so only use 156 when there are no other possibilities""" 157 try: 158 return self.owner.get_component(StorageComponent).inventory 159 except AttributeError: # no owner or no inventory, either way, we don't care 160 return None
161
162 - def pickup_resources(self, res, amount, collector):
163 """Try to get amount number of resources of id res_id that are in stock 164 and removes them from the stock. Will return smaller amount if not 165 enough resources are available. 166 @param res: int resource id 167 @param amount: int amount that is to be picked up 168 @param collector: the collector instance, that picks it up 169 @return: int number of resources that can actually be picked up""" 170 picked_up = self.get_available_pickup_amount(res, collector) 171 assert picked_up >= 0 172 if picked_up > amount: 173 picked_up = amount 174 remnant = self.get_component(StorageComponent).inventory.alter(res, -picked_up) 175 assert remnant == 0 176 return picked_up
177
178 - def get_available_pickup_amount(self, res, collector):
179 """Returns how much of res a collector may pick up. It's the stored amount minus the amount 180 that other collectors are getting""" 181 if res not in self.provided_resources: 182 return 0 # we don't provide this, and give nothing away because we need it ourselves. 183 else: 184 amount_from_collectors = sum((entry.amount 185 for c in self.__incoming_collectors 186 for entry in c.job.reslist 187 if c is not collector and 188 entry.res == res)) 189 amount = self.get_component(StorageComponent).inventory[res] - amount_from_collectors 190 # the user can take away res, even if a collector registered for them 191 # if this happens, a negative number would be returned. Use 0 instead. 192 return max(amount, 0)
193 194 ## PROTECTED METHODS
195 - def _load_provided_resources(self):
196 """Returns a iterable obj containing all resources this building provides. 197 This is outsourced from initialization to a method for the possibility of 198 overwriting it. 199 Do not alter the returned list; if you need to do so, then copy it.""" 200 produced_resources = set() 201 for prod in self.get_component(Producer).get_productions(): 202 for res in prod.get_produced_resources(): 203 produced_resources.add(res) 204 205 for res in self.additional_provided_resources: 206 produced_resources.add(res) 207 208 return produced_resources
209 210
211 -class StorageResourceHandler(ResourceHandler):
212 """Same as ResourceHandler, but for storage buildings such as warehouses. 213 Provides all tradeable resources.""" 214
215 - def get_consumed_resources(self):
216 """We collect everything we provide""" 217 return self.provided_resources
218
219 - def _load_provided_resources(self):
220 """Storages provide every res. 221 Do not alter the returned list; if you need to do so, then copy it.""" 222 return self.session.db.get_res(only_tradeable=True)
223