import copy from aoe.types import * from aoe.exceptions import * class Gather(): # Gathering occurs when a villager is assigned to some relevant Task. # # Each Task consumes a map resource (e.g., a boar) at some rate. # That rate is recorded in gather_rates. # # Map resources such as boars should not be confused with a player's # resource stockpile. For example, Wood as a map resource is consumed by # gathering, whereas the player's Wood resource amount is increased. As # another example, a boar is a map resource that is consumed and leads to # an increase in Food in the player's stockpile. # # When a Task is performed and a map resource is consumed, one or more # resources are added to the player's stockpile. The typical case is to # add one resource. E.g., when chopping wood, wood is added to the # player's stockpile. However, for some civs or technologies, more than one # resource may be gathered by a single Task. For example, there is a # technology that allows woodcutters to generate food as they gather wood. # # The resources gathered by a Task are recorded in gathered_resources. # Each Task is associated with 0 or more Resource->Int key-value pairs. # A Task gathers all resources associated with itself. The integer values # in the aforementioned pairs are multipliers. A value of 1 indicates # that the corresponding resource will be added to the player's stockpile # at the same rate that the world's resource is consumed. A higher value # indicates that the resource is added at a more favorable ratio to the # player, whereas a lower value indicates a less favorable ratio. # # To reiterate, a value in gather_rates indicates how quickly a world # resource is removed. It may have been better named "consumption_rates" # or something similar. On the other hand, to determine how quickly a # resource is actually added to the player's stockpile, you must consider # the gather rate from gather_rates *and* the corresponding multiplier from # gathered_resources. # # Buildings # # # Most gathering cannot be performed without one or more buildings. In # many cases, there are a few options for a building or set of buildings # that will allow the resource to be gathered. The dictionary # required_gather_buildings records which buildings are necessary and # sufficient to perform each gathering Task. For a given Task, there is a # corresponding list of sets. If the world contains *all* of the buildings # in *any* of those sets, then the Task can be performed. Otherwise, it # cannot. # # Varying Rates and Resource Exhaustion # # # The rate of consumption in gather_rates is not fixed over time. In # general, as a task is performed, the rate will either slow down or stop # entirely. For example, a lumber camp gets "stale" and the rate will drop # unless a new camp is built, and a gold mine will be exhausted and the # rate will effectively drop to zero until a new mine is built. # # Several data structures are involved in the implementation of this idea. # We assume the world at any given time has some amount of consumables # accessible to a player, which is stored in gatherable_amounts. This is # *not* the total amount of, e.g., wood available on the map. It is # instead the amount of, e.g., wood that can be consumed *at the current # rate* given the available buildings. This amount goes down as resources # are gathered and can go up as new buildings, such as lumber camps, are # built. Resources are subtracted from gatherable_amounts at a rate # determined by gather_rates and the number of villagers performing a Task. # # When the amount drops below 0, either the gather rate decreases (as in # the case of cutting wood) or that Task stops entirely (as in the case of # mining gold). If the rate merely decreases, the process repeats itself. # That is, the value in gatherable_amounts is increased, the value in # gather_rates is decreased, and another decrease will occur when the new # amount in gatherable_amounts is exhausted. The dictionary # gather_decrease_rates contains the multipliers by which the gather rate # is decreased when resource exhaustion occurs. A value of 0 indicates # that Task is stopped entirely. # # When a new building relevant for gathering, such as a lumber camp, is # created, the amount of the corresponding consumable in gatherable_amounts # is increased. If the gather rate was already at its original value, then # some fixed amount is simply added to the available consumable, which will # delay the next rate decrease. If the corresponding gather rate had # previously been decreased, that rate is reset to its ideal value. In # that case, the amount of available consumable is set to a value rather # than added to the current value because the current value was available # at a lower gather rate. # # A given building can provide access to various resources and may provide # access to more than one. For example, a mining camp could provide access # to gold, stone, both, or neither. The dictionary # buildings_gather_increase stores information about which Task or group of # Tasks can have their consumable amount increased when a particular # building is built. A user can specify which Tasks, if any, benefit from # a particular instance of a building, but the selected Tasks must be # available for that building according to buildings_gather_increase for # the choice to be considered valid. This is especially valuable for Town # Centers, which can be built near any number of resources. That same data # structure also stores the amount by which the relevant consumables are # increased (which is also the reset value for that entry of # gatherable_amounts in the case where a rate decrease has already # occurred). # # Note that gathered_resources, which contains the multipliers for # gather_rates and determines how much of a resource the player actually # receives, is not relevant for determining how quickly a resource is # exhausted. That calculation involves solely gatherable_amounts, # gather_rates, the number of villagers performing the Task, and the amount # of simulated time that passes. # # Known Limitations # # # There is currently no penalty for having too many villagers gathering at # a particular building (e.g., 100 lumberjacks at a single wood camp). # However, if there are not enough lumber camps, the scheduled decrease # will happen quickly in that case, which will help penalize it somewhat. # # We also do not put a ceiling on the amount of a world resource that can # be accessed. For example, we do not have a fixed amount of berries. If # a user continues building mills and assigning more villagers to berries, # they can do so indefinitely. We are trusting the user to know how many # resources are available on their map. # # Farming # # # Farming is a little different, and I don't feel like explaining it right # now. # BIG_NUMBER = 1e6 def check_gather(self, world, task, count=1): if task in [Tasks.IDLE, Tasks.BUILD]: return required = self.required_gather_buildings[task] have = set(world.buildings) match = any(map(have.issuperset, required)) if not match: raise GatherBuildingNotAvailableException if task == task.FARM: if world.villager_tasks[task] + count > len(world.unused_farms): raise GatherBuildingNotAvailableException def __init__(self): self.gather_rates = { Tasks.BERRIES: 18.6/60, Tasks.SHEEP: 19.8/60, Tasks.BOAR: 24.6/60, Tasks.HUNT: 24.6/60, Tasks.FARM: 20/60, Tasks.FISH_VILLAGER: 25.8/60, Tasks.FISH_SHIP_SHORE: 16.8/60, Tasks.FISH_SHIP_DEEP: 29.4/60, Tasks.FISHTRAP: 21/60, Tasks.WOOD: 23.4/60, Tasks.GOLD: 22.8/60, Tasks.TRADECART: 25.9/60, Tasks.TRADECOG: 25.9/60, Tasks.RELIC: 30/60, Tasks.STONE: 21.6/60, Tasks.BUILD: 0, Tasks.IDLE: 0, } self.original_gather_rates = copy.deepcopy(self.gather_rates) self.gathered_resources = { Tasks.BERRIES: {Resources.FOOD: 1}, Tasks.SHEEP: {Resources.FOOD: 1}, Tasks.BOAR: {Resources.FOOD: 1}, Tasks.HUNT: {Resources.FOOD: 1}, Tasks.FARM: {Resources.FOOD: 1}, Tasks.FISH_VILLAGER: {Resources.FOOD: 1}, Tasks.FISH_SHIP_SHORE: {Resources.FOOD: 1}, Tasks.FISH_SHIP_DEEP: {Resources.FOOD: 1}, Tasks.FISHTRAP: {Resources.FOOD: 1}, Tasks.WOOD: {Resources.WOOD: 1}, Tasks.GOLD: {Resources.GOLD: 1}, Tasks.TRADECART: {Resources.GOLD: 1}, Tasks.TRADECOG: {Resources.GOLD: 1}, Tasks.RELIC: {Resources.GOLD: 1}, Tasks.STONE: {Resources.STONE: 1}, Tasks.BUILD: {}, Tasks.IDLE: {}, } self.required_gather_buildings = { Tasks.BERRIES: [set([Buildings.MILL]), set([Buildings.TOWNCENTER])], Tasks.SHEEP: [set([Buildings.MILL]), set([Buildings.TOWNCENTER])], Tasks.BOAR: [set([Buildings.MILL]), set([Buildings.TOWNCENTER])], Tasks.HUNT: [set([Buildings.MILL]), set([Buildings.TOWNCENTER])], Tasks.FARM: [set([Buildings.FARM, Buildings.MILL]), set([Buildings.FARM, Buildings.TOWNCENTER])], Tasks.FISH_VILLAGER: [set([Buildings.DOCK]), set([Buildings.MILL]), set([Buildings.TOWNCENTER])], Tasks.FISH_SHIP_SHORE: [set([Buildings.DOCK])], Tasks.FISH_SHIP_DEEP: [set([Buildings.DOCK])], Tasks.FISHTRAP: [set([Buildings.DOCK, Buildings.FISHTRAP])], Tasks.WOOD: [set([Buildings.LUMBERCAMP]), set([Buildings.TOWNCENTER])], Tasks.GOLD: [set([Buildings.MININGCAMP]), set([Buildings.TOWNCENTER])], Tasks.TRADECART: [set([Buildings.MARKET])], Tasks.TRADECOG: [set([Buildings.DOCK])], #Tasks.RELIC: [set([Buildings.MONASTERY])], Tasks.STONE: [set([Buildings.MININGCAMP]), set([Buildings.TOWNCENTER])], Tasks.BUILD: [], Tasks.IDLE: [], } self.gatherable_amounts = { # These will vary based on the map. # I am assuming Arabia with: # 6 forage bushes # 8 sheep # 2 boars # 4 deer Tasks.BERRIES: 6*125, Tasks.SHEEP: 8*100, Tasks.BOAR: 2*340, Tasks.HUNT: 4*140, Tasks.FARM: 0, # TODO next two should be shared Tasks.FISH_VILLAGER: 0, Tasks.FISH_SHIP_SHORE: 0, Tasks.FISH_SHIP_DEEP: 0, Tasks.FISHTRAP: 0, Tasks.WOOD: 0, Tasks.GOLD: 0, Tasks.TRADECART: Gather.BIG_NUMBER, Tasks.TRADECOG: Gather.BIG_NUMBER, Tasks.RELIC: Gather.BIG_NUMBER, Tasks.STONE: 0, } self.buildings_gather_increase = { # Made-up numbers for now Buildings.LUMBERCAMP: {Tasks.WOOD: 1000}, Buildings.MILL: {Tasks.HUNT: 4*140, Tasks.BERRIES: 6*125}, Buildings.MININGCAMP: {Tasks.GOLD: 4*800, Tasks.STONE: 4*350}, Buildings.FARM: {Tasks.FARM: 175}, #Buildings.DOCK: {Tasks.WOOD: 100}, Buildings.TOWNCENTER: {Tasks.WOOD: 2000, Tasks.GOLD: 4*800, Tasks.STONE: 4*350, Tasks.BERRIES: 6*125, Tasks.HUNT: 4*140} #Buildings.FISHTRAP: {Tasks.WOOD: 100}, } self.gather_decrease_rates = { # Made-up numbers for now Tasks.BERRIES: 0, Tasks.SHEEP: 0, Tasks.BOAR: 0, Tasks.HUNT: 0, Tasks.FARM: 0, Tasks.FISH_VILLAGER: 0.5, Tasks.FISH_SHIP_SHORE: 0.5, Tasks.FISH_SHIP_DEEP: 0, Tasks.FISHTRAP: 0, Tasks.WOOD: 0.85, Tasks.GOLD: 0, Tasks.TRADECART: 1, Tasks.TRADECOG: 1, Tasks.RELIC: 1, Tasks.STONE: 0, }