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

Source Code for Module horizons.world.storage

  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   
 23  """ 
 24  Here we define how the inventories work, that are used by world objects. 
 25  These storage classes exist: 
 26   
 27  - GenericStorage (abstract): Defines general interface for storage. 
 28   
 29  Storages with certain properties: 
 30  - PositiveStorage: Doesn't allow negative values. 
 31  - TotalStorage: Sum of all stored res must be <= a certain limit. 
 32  - SpecializedStorage: Allows only certain resources to be stored here. 
 33  - SizedSpecializedStorage: Like SpecializedStorage, but each res has an own limit. 
 34   
 35  Combinations: 
 36  - SizedSlottedStorage: One limit, each res value must be <= the limit and >= 0. 
 37  - PositiveTotalStorage: use case: ship inventory 
 38  - PositiveSizedSlotStorage: every res has the same limit, only positive values (warehouse, collectors) 
 39  - PositiveSizedSpecializedStorage: Like SizedSpecializedStorage, plus only positive values. 
 40  """ 
 41   
 42  import copy 
 43  import sys 
 44  from collections import defaultdict 
 45   
 46  from horizons.util.changelistener import ChangeListener 
 47   
 48   
49 -class GenericStorage(ChangeListener):
50 """The GenericStorage represents a storage for buildings/units/players/etc. for storing 51 resources. The GenericStorage is the general form and is mostly used as baseclass to 52 derive storages with special function from it. Normally there should be no need to 53 use the GenericStorage. Rather use a specialized version that is suitable for the job. 54 """
55 - def __init__(self):
56 super().__init__() 57 self._storage = defaultdict(int)
58
59 - def save(self, db, ownerid):
60 for slot in self._storage.items(): 61 db("INSERT INTO storage (object, resource, amount) VALUES (?, ?, ?) ", 62 ownerid, slot[0], slot[1])
63
64 - def load(self, db, ownerid):
65 for (res, amount) in db.get_storage_rowids_by_ownerid(ownerid): 66 self.alter(res, amount)
67
68 - def alter(self, res, amount):
69 """alter() will return the amount of resources that did not fit into the storage or 70 if altering in a negative way to remove resources, the amount of resources that was 71 not available in the storage. The totalstorage always returns 0 as there are not 72 limits as to what can be in the storage. 73 @param res: int res id that is to be altered 74 @param amount: int amount that is to be changed. Can be negative to remove resources. 75 @return: int - amount that did not fit or was not available, depending on context. 76 """ 77 self._storage[res] += amount # defaultdict 78 self._changed() 79 return 0
80
81 - def reset(self, res):
82 """Resets a resource slot to zero, removing all its contents.""" 83 if res in self._storage: 84 self._storage[res] = 0 85 self._changed()
86
87 - def reset_all(self):
88 """Removes every resource from this inventory""" 89 for res in self._storage: 90 self._storage[res] = 0 91 self._changed()
92
93 - def get_limit(self, res=None):
94 """Returns the current limit of the storage. Please note that this 95 value can have different meanings depending on the context. See the 96 storage descriptions on what the value does in each case. 97 @param res: int res that the limit should be returned for. 98 @return: int 99 """ 100 return sys.maxsize # should not be used for generic storage
101
102 - def get_free_space_for(self, res):
103 """Returns how much of res we can still store here (limit - current amount).""" 104 return self.get_limit(res) - self[res]
105
107 return sum(self._storage.values())
108
109 - def get_dump(self):
110 """Returns a dump of the inventory as dict""" 111 return copy.deepcopy(self._storage)
112
113 - def __getitem__(self, res):
114 return self._storage.get(res, 0)
115
116 - def iterslots(self):
117 return iter(self._storage.keys())
118
119 - def itercontents(self):
120 return iter(self._storage.items())
121
122 - def __str__(self):
123 return "{}({})".format(self.__class__, self._storage if hasattr(self, "_storage") else None)
124 125
126 -class SpecializedStorage(GenericStorage):
127 """Storage where only certain resources can be stored. If you want to store a resource here, 128 you have to call add_resource_slot() before calling alter()."""
129 - def alter(self, res, amount):
130 if self.has_resource_slot(res): # res can be stored, propagate call 131 return super().alter(res, amount) 132 else: 133 return amount # we couldn't store any of this
134
135 - def add_resource_slot(self, res):
136 """Creates a slot for res. Does nothing if the slot exists.""" 137 super().alter(res, 0) 138 self._changed()
139
140 - def has_resource_slot(self, res):
141 return (res in self._storage)
142 143
144 -class SizedSpecializedStorage(SpecializedStorage):
145 """Just like SpecializedStorage, but each res has an own limit. 146 Can take a dict {res: size, res2: size2, ...} to init slots 147 """
148 - def __init__(self, slot_sizes=None):
149 super().__init__() 150 slot_sizes = slot_sizes or {} 151 self.__slot_limits = {} 152 for res, size in slot_sizes.items(): 153 self.add_resource_slot(res, size)
154
155 - def alter(self, res, amount):
156 if not self.has_resource_slot(res): 157 # TODO: this is also checked in the super class, refactor one of them away 158 return amount 159 160 if amount > 0: # can only reach limit if > 0 161 storeable_amount = self.get_free_space_for(res) 162 if amount > storeable_amount: # tried to store more than limit allows 163 ret = super().alter(res, storeable_amount) 164 return (amount - storeable_amount) + ret 165 166 # no limit breach, just propagate call 167 return super().alter(res, amount)
168
169 - def get_limit(self, res):
170 return self.__slot_limits.get(res, 0)
171
172 - def add_resource_slot(self, res, size):
173 """Add a resource slot for res that can hold at most *size* units. 174 If the slot already exists, just update its size to *size*. 175 NOTE: THIS IS NOT SAVE/LOADED HERE. It must be restored manually.""" 176 super().add_resource_slot(res) 177 assert size >= 0 178 self.__slot_limits[res] = size
179
180 - def save(self, db, ownerid):
181 super().save(db, ownerid) 182 assert len(self._storage) == len(self.__slot_limits) # we have to have limits for each res
183
184 - def load(self, db, ownerid):
185 super().load(db, ownerid)
186 187
188 -class GlobalLimitStorage(GenericStorage):
189 """Storage with some kind of global limit. This limit has to be 190 interpreted in the subclass, it has no predefined meaning here. 191 This class is used for infrastructure, such as save/load for the limit."""
192 - def __init__(self, limit):
193 super().__init__() 194 self.limit = limit
195
196 - def save(self, db, ownerid):
197 super().save(db, ownerid) 198 db("INSERT INTO storage_global_limit(object, value) VALUES(?, ?)", ownerid, self.limit)
199
200 - def load(self, db, ownerid):
201 self.limit = db.get_storage_global_limit(ownerid) 202 super().load(db, ownerid)
203
204 - def adjust_limit(self, amount):
205 """Adjusts the limit of the storage by amount. 206 If the limit is reduced, every resource that doesn't fit in the storage anymore is dropped! 207 @param amount: int, difference to current limit (positive or negative) 208 """ 209 self.limit += amount 210 if self.limit < 0: 211 self.limit = 0 212 # remove res that don't fit anymore 213 for res, amount in self._storage.items(): 214 if amount > self.limit: 215 self._storage[res] = self.limit 216 self._changed()
217
218 - def get_limit(self, res=None):
219 return self.limit
220 221
222 -class TotalStorage(GlobalLimitStorage):
223 """The TotalStorage represents a storage with a general limit to the sum of resources 224 that can be stored in it. The limit is a general limit, not specialized to one resource. 225 E.g. if the limit is 10, you can have 4 items of res A and 6 items of res B, then nothing 226 else can be stored here. 227 228 NOTE: Negative values will increase storage size, so consider using PositiveTotalStorage. 229 """
230 - def __init__(self, limit):
231 super().__init__(limit)
232
233 - def alter(self, res, amount):
234 check = max(0, amount + self.get_sum_of_stored_resources() - self.limit) 235 return check + super().alter(res, amount - check)
236
237 - def get_free_space_for(self, res):
238 return self.limit - self.get_sum_of_stored_resources()
239 240
241 -class PositiveStorage(GenericStorage):
242 """The positive storage doesn't allow to have negative values for resources."""
243 - def alter(self, res, amount):
244 subtractable_amount = amount 245 if amount < 0 and (amount + self[res] < 0): # tried to subtract more than we have 246 subtractable_amount = - self[res] # only amount where we keep a positive value 247 ret = super().alter(res, subtractable_amount) 248 return (amount - subtractable_amount) + ret
249 250
251 -class PositiveTotalStorage(PositiveStorage, TotalStorage):
252 """A combination of the Total and Positive storage. Used to set a limit and ensure 253 there are no negative amounts in the storage."""
254 - def alter(self, res, amount):
255 ret = super().alter(res, amount) 256 if self[res] == 0: 257 # remove empty slots, cause else they will get displayed in the ship inventory 258 del self._storage[res] 259 return ret
260 261
262 -class PositiveTotalNumSlotsStorage(PositiveStorage, TotalStorage):
263 """A combination of the Total and Positive storage which only has a limited number of slots. 264 Used to set a limit and ensure there are no negative amounts in the storage."""
265 - def __init__(self, limit, slotnum):
266 super().__init__(limit) 267 self.slotnum = slotnum
268
269 - def alter(self, res, amount):
270 if amount == 0: 271 return 0 272 if res not in self._storage and len(self._storage) >= self.slotnum: 273 return amount 274 ret = super().alter(res, amount) 275 if self[res] == 0: 276 # remove empty slots, cause else they will get displayed in the ship inventory 277 del self._storage[res] 278 return ret
279
280 - def get_free_space_for(self, res):
281 if res not in self._storage and len(self._storage) >= self.slotnum: 282 return 0 283 else: 284 return super().get_free_space_for(res)
285 286
287 -class PositiveSizedSlotStorage(GlobalLimitStorage, PositiveStorage):
288 """A storage consisting of a slot for each resource, all slots have the same size 'limit' 289 Used by the warehouse for example. So with a limit of 30 you could have a max of 290 30 from each resource."""
291 - def __init__(self, limit=0):
292 super().__init__(limit)
293
294 - def alter(self, res, amount):
295 check = max(0, amount + self[res] - self.limit) 296 ret = super().alter(res, amount - check) 297 if res in self._storage and self[res] == 0: 298 del self._storage[res] 299 return check + ret
300 301
302 -class PositiveSizedSpecializedStorage(PositiveStorage, SizedSpecializedStorage):
303 pass
304 305
306 -class PositiveSizedNumSlotStorage(PositiveSizedSlotStorage):
307 """A storage consisting of a number of slots, all slots have the same size 'limit'. 308 Used by ships for example. With a limit of 50 and a slot num of 4, you 309 could have a max of 50 from each resource and only slotnum resources."""
310 - def __init__(self, limit, slotnum):
311 super().__init__(limit) 312 self.slotnum = slotnum
313
314 - def alter(self, res, amount):
315 if amount == 0: 316 return 0 317 if res not in self._storage and len(self._storage) >= self.slotnum: 318 return amount 319 result = super().alter(res, amount) 320 return result
321
322 - def get_free_space_for(self, res):
323 if res not in self._storage and len(self._storage) >= self.slotnum: 324 return 0 325 else: 326 return super().get_free_space_for(res)
327 328 329 ########################################################################
330 -class SettlementStorage:
331 """Dummy class to signal the storagecomponent to use the settlements inventory"""
332