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

Source Code for Module horizons.world.units.animal

  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  import logging 
 23   
 24  from horizons.command.unit import CreateUnit 
 25  from horizons.component.storagecomponent import StorageComponent 
 26  from horizons.constants import RES, WILD_ANIMAL 
 27  from horizons.scheduler import Scheduler 
 28  from horizons.util.pathfinding.pather import SoldierPather 
 29  from horizons.util.worldobject import WorldObject 
 30  from horizons.world.resourcehandler import ResourceHandler 
 31  from horizons.world.units.collectors import Collector, Job 
 32   
 33   
34 -class Animal(ResourceHandler):
35 """Base Class for all animals. An animal is a unit, that consumes resources (e.g. grass) 36 and usually produce something (e.g. wool, meat).""" 37 log = logging.getLogger('world.units.animal') 38
39 - def __init__(self, *args, **kwargs):
40 super().__init__(*args, **kwargs)
41 42
43 -class CollectorAnimal(Animal):
44 """Animals that will inherit from collector"""
45 - def __init__(self, **kwargs):
46 super().__init__(**kwargs) 47 self.__init()
48
49 - def __init(self):
50 self.collector = None
51
52 - def load(self, db, worldid):
53 self.__init() 54 super().load(db, worldid)
55
56 - def apply_state(self, state, remaining_ticks=None):
57 super().apply_state(state, remaining_ticks) 58 if self.state == self.states.no_job_walking_randomly: 59 self.add_move_callback(self.search_job)
60
61 - def stop_after_job(self, collector):
62 """Tells the unit to stop after the current job and call the collector to pick it up""" 63 self.collector = collector
64
65 - def remove_stop_after_job(self):
66 """Let the animal continue as usual after the job. Can only be called 67 after stop_after_job""" 68 assert self.collector is not None 69 self.collector = None
70
71 - def has_collectors(self):
72 """Whether this unit is just now or about to be collected""" 73 return self.collector is not None or self.state == self.states.waiting_for_herder
74
75 - def finish_working(self):
76 # animal is done when it has eaten, and 77 # doesn't have to get home, so end job right now 78 Collector.finish_working(self) 79 self.end_job()
80
81 - def search_job(self):
82 """Search for a job, only called if the collector does not have a job.""" 83 self.log.debug("%s search job", self) 84 if self.collector is not None: 85 # tell the animalcollector to pick me up 86 collector = self.collector 87 self.collector = None 88 collector.pickup_animal() 89 self.state = self.states.waiting_for_herder 90 else: 91 super().search_job()
92
93 - def get_home_inventory(self):
95
96 - def get_collectable_res(self):
97 return self.get_needed_resources()
98 99
100 -class WildAnimal(CollectorAnimal, Collector):
101 """Animals, that live in the nature and feed on natural resources. 102 These animals can be hunted. 103 104 They produce wild animal meat and feed on wild animal food x, which is produced by 105 e.g. a tree. 106 107 It is assumed, that they need all resources, that they use, for reproduction. If they have 108 gathered all resources, and their inventory is full, they reproduce. 109 """ 110 walking_range = 6 111 work_duration = 96 112 pather_class = SoldierPather 113
114 - def __init__(self, owner, start_hidden=False, can_reproduce=True, **kwargs):
115 super().__init__(start_hidden=start_hidden, owner=owner, **kwargs) 116 self.__init(owner, can_reproduce) 117 self.log.debug("Wild animal %s created at " + str(self.position) + 118 "; can_reproduce: %s; population now: %s", 119 self.worldid, can_reproduce, len(self.home_island.wild_animals))
120
121 - def __init(self, island, can_reproduce, health=None):
122 """ 123 @param island: Hard reference to island 124 @param can_reproduce: bool 125 @param health: int or None 126 """ 127 assert isinstance(can_reproduce, bool) 128 # good health is the main target of an animal. it increases when they eat and 129 # decreases, when they have no food. if it reaches 0, they die, and 130 # if it reaches HEALTH_LEVEL_TO_REPRODUCE, they reproduce 131 self.health = health if health is not None else WILD_ANIMAL.HEALTH_INIT_VALUE 132 self.can_reproduce = can_reproduce 133 self.home_island = island 134 self.home_island.wild_animals.append(self) 135 136 resources = self.get_needed_resources() 137 assert resources in [[RES.WILDANIMALFOOD], []] 138 self._required_resource_id = RES.WILDANIMALFOOD 139 self._building_index = self.home_island.get_building_index(self._required_resource_id)
140
141 - def save(self, db):
142 super().save(db) 143 # save members 144 db("INSERT INTO wildanimal(rowid, health, can_reproduce) VALUES(?, ?, ?)", 145 self.worldid, self.health, int(self.can_reproduce)) 146 # set island as owner 147 db("UPDATE unit SET owner = ? WHERE rowid = ?", self.home_island.worldid, self.worldid) 148 149 # save remaining ticks when in waiting state 150 if self.state == self.states.no_job_waiting: 151 calls = Scheduler().get_classinst_calls(self, self.handle_no_possible_job) 152 assert len(calls) == 1, 'calls: {}'.format(calls) 153 remaining_ticks = max(list(calls.values())[0], 1) # we have to save a number > 0 154 db("UPDATE collector SET remaining_ticks = ? WHERE rowid = ?", 155 remaining_ticks, self.worldid)
156
157 - def load(self, db, worldid):
158 super().load(db, worldid) 159 # get own properties 160 health, can_reproduce = db.get_wildanimal_row(worldid) 161 # get home island 162 island = WorldObject.get_object_by_id(db.get_unit_owner(worldid)) 163 self.__init(island, bool(can_reproduce), health)
164
165 - def get_collectable_res(self):
166 return [self._required_resource_id]
167
168 - def apply_state(self, state, remaining_ticks=None):
169 super().apply_state(state, remaining_ticks) 170 if self.state == self.states.no_job_waiting: 171 Scheduler().add_new_object(self.handle_no_possible_job, self, remaining_ticks)
172
173 - def handle_no_possible_job(self):
174 """Just walk to a random location nearby and search there for food, when we arrive""" 175 self.log.debug('%s: no possible job; health: %s', self, self.health) 176 # decrease health because of lack of food 177 self.health -= WILD_ANIMAL.HEALTH_DECREASE_ON_NO_JOB 178 if self.health <= 0: 179 self.die() 180 return 181 182 # if can't find a job, we walk to a random location near us and search there 183 (target, path) = self.get_random_location(self.walking_range) 184 if target is not None: 185 self.log.debug('%s: no possible job, walking to %s', self, str(target)) 186 self.move(target, callback=self.search_job, path=path) 187 self.state = self.states.no_job_walking_randomly 188 else: 189 # we couldn't find a target, just try again 3 secs later 190 self.log.debug('%s: no possible job, no possible new loc', self) 191 Scheduler().add_new_object(self.handle_no_possible_job, self, 48) 192 self.state = self.states.no_job_waiting
193
194 - def get_job(self):
195 pos = self.position.to_tuple() 196 197 # try to get away with a random job (with normal forest density this works > 99% of the time) 198 for i in range(min(5, self._building_index.get_num_buildings_in_range(pos))): 199 provider = self._building_index.get_random_building_in_range(pos) 200 if provider is not None and self.check_possible_job_target(provider): 201 # animals only collect one resource 202 entry = self.check_possible_job_target_for(provider, self._required_resource_id) 203 if entry: 204 path = self.check_move(provider.loading_area) 205 if path: 206 job = Job(provider, [entry]) 207 job.path = path 208 return job 209 210 # NOTE: use random job, works fine and is faster than looking for the best 211 return None
212
213 - def check_possible_job_target(self, provider):
214 if provider.position.contains(self.position): 215 # force animal to choose a tree where it currently not stands 216 return False 217 return super().check_possible_job_target(provider)
218
219 - def end_job(self):
220 super().end_job() 221 # check if we can reproduce 222 self.log.debug("%s end_job; health: %s", self, self.health) 223 self.health += WILD_ANIMAL.HEALTH_INCREASE_ON_FEEDING 224 if self.can_reproduce and self.health >= WILD_ANIMAL.HEALTH_LEVEL_TO_REPRODUCE and \ 225 len(self.home_island.wild_animals) < (self.home_island.num_trees // WILD_ANIMAL.POPULATION_LIMIT): 226 self.reproduce() 227 # reproduction costs health 228 self.health = WILD_ANIMAL.HEALTH_INIT_VALUE
229
230 - def reproduce(self):
231 """Create another animal of our type on the place where we stand""" 232 if not self.can_reproduce: 233 return 234 235 self.log.debug("%s REPRODUCING", self) 236 # create offspring 237 CreateUnit(self.owner.worldid, self.id, self.position.x, self.position.y, 238 can_reproduce=self.next_clone_can_reproduce())(issuer=None) 239 # reset own resources 240 for res in self.get_consumed_resources(): 241 self.get_component(StorageComponent).inventory.reset(res)
242
243 - def next_clone_can_reproduce(self):
244 """Returns, whether the next child will be able to reproduce himself. 245 Some animal can't reproduce, which makes population growth easier to control. 246 @return: bool""" 247 return (self.session.random.randint(0, 2) > 0) # 2/3 chance for True
248
249 - def die(self):
250 """Makes animal die, e.g. because of starvation or getting killed by herder""" 251 self.log.debug("%s dying", self) 252 self.home_island.wild_animals.remove(self) 253 self.remove()
254
255 - def cancel(self, continue_action=None):
256 if continue_action is None: 257 continue_action = self.search_job 258 super().cancel(continue_action=continue_action)
259
260 - def __str__(self):
261 return "{}(health={})".format(super().__str__(), 262 getattr(self, 'health', None))
263