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

Source Code for Module horizons.world.concreteobject

  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 random 
 23   
 24  from horizons.constants import ACTION_SETS 
 25  from horizons.messaging import ActionChanged 
 26  from horizons.scheduler import Scheduler 
 27  from horizons.util.loaders.actionsetloader import ActionSetLoader 
 28  from horizons.util.python.callback import Callback 
 29  from horizons.util.worldobject import WorldObject 
 30  from horizons.world.units import UnitClass 
31 32 33 -class ConcreteObject(WorldObject):
34 """Class for concrete objects like Units or Buildings. 35 "Concrete" here means "you can touch it", e.g. a Warehouse is a ConcreteObject, 36 a Settlement isn't. 37 All such objects have positions, so Islands are no ConcreteObjects for technical reasons. 38 39 Assumes that object has a member _instance. 40 """ 41 movable = False # whether instance can move 42 is_unit = False 43 is_building = False 44
45 - def __init__(self, session, action_set_id=None, **kwargs):
46 """ 47 @param session: Session instance this obj belongs to 48 """ 49 super().__init__(**kwargs) 50 from horizons.session import Session 51 assert isinstance(session, Session) 52 self.session = session 53 self.__init(action_set_id)
54
55 - def __init(self, action_set_id=None):
56 # overwrite in subclass __init[__] 57 self._instance = None 58 # Default action is 'idle' 59 self._action = 'idle' 60 # NOTE: this can't be level-aware since not all ConcreteObjects have levels 61 self._action_set_id = action_set_id if action_set_id else self.__class__.get_random_action_set() 62 63 # only buildings for now 64 # NOTE: this is player dependent, therefore there must be no calls to session.random that depend on this 65 self.has_status_icon = self.is_building and self.show_status_icons and \ 66 self.owner is not None and self.owner.is_local_player # and only for the player's buildings
67 68 @property
69 - def fife_instance(self):
70 return self._instance
71
72 - def save(self, db):
73 super().save(db) 74 db("INSERT INTO concrete_object(id, action_runtime, action_set_id) VALUES(?, ?, ?)", self.worldid, 75 self._instance.getActionRuntime(), self._action_set_id)
76
77 - def load(self, db, worldid):
78 super().load(db, worldid) 79 runtime, action_set_id = db.get_concrete_object_data(worldid) 80 # action_set_id should never be None in regular games, 81 # but this information was lacking in savegames before rev 59. 82 if action_set_id is None: 83 action_set_id = self.__class__.get_random_action_set(level=self.level if hasattr(self, "level") else 0) 84 self.__init(action_set_id) 85 86 # delay setting of runtime until load of sub/super-class has set the action 87 def set_action_runtime(self, runtime): 88 # workaround to delay resolution of self._instance, which doesn't exist yet 89 self._instance.setActionRuntime(runtime)
90 Scheduler().add_new_object(Callback(set_action_runtime, self, runtime), self, run_in=0)
91
92 - def act(self, action, facing_loc=None, repeating=False, force_restart=True):
93 """ 94 @param repeating: maps to fife instance method actRepeat or actOnce 95 @param force_restart: whether to always restart, even if action is already displayed 96 """ 97 if not self.has_action(action): 98 action = 'idle' 99 100 if not force_restart and self._action == action: 101 return 102 103 self._action = action 104 105 # TODO This should not happen, this is a fix for the component introduction 106 # Should be fixed as soon as we move concrete object to a component as well 107 # which ensures proper initialization order for loading and initing 108 if self._instance is None: 109 return 110 111 if facing_loc is None: 112 facing_loc = self._instance.getFacingLocation() 113 UnitClass.ensure_action_loaded(self._action_set_id, action) # lazy 114 if repeating: 115 self._instance.actRepeat(action + "_" + str(self._action_set_id), facing_loc) 116 else: 117 self._instance.actOnce(action + "_" + str(self._action_set_id), facing_loc)
118
119 - def has_action(self, action):
120 """Checks if this unit has a certain action. 121 @param action: animation id as string""" 122 return (action in ActionSetLoader.get_set(self._action_set_id))
123
124 - def remove(self):
125 self._instance.getLocationRef().getLayer().deleteInstance(self._instance) 126 self._instance = None 127 Scheduler().rem_all_classinst_calls(self) 128 super().remove()
129 130 @classmethod
131 - def weighted_choice(cls, weighted_dict):
132 """ http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/ 133 """ 134 # usually we do not need any magic because there only is one set: 135 if len(weighted_dict) == 1: 136 return list(weighted_dict.keys())[0] 137 weights = sum(ACTION_SETS.DEFAULT_WEIGHT if w is None else w 138 for i, w in weighted_dict.items()) 139 rnd = random.random() * weights 140 for action_set, weight in weighted_dict.items(): 141 rnd -= ACTION_SETS.DEFAULT_WEIGHT if weight is None else weight 142 if rnd < 0: 143 return action_set
144 145 @classmethod
146 - def get_random_action_set(cls, level=0, exact_level=False):
147 """Returns an action set for an object of type object_id in a level <= the specified level. 148 The highest level number is preferred. 149 @param level: level to prefer. a lower level might be chosen 150 @param exact_level: choose only action sets from this level. return val might be None here. 151 @return: action_set_id or None""" 152 action_sets = cls.action_sets 153 action_set = None 154 if exact_level: 155 if level in action_sets: 156 action_set = cls.weighted_choice(action_sets[level]) 157 # if there isn't one, stick with None 158 else: # search all levels for an action set, starting with highest one 159 for possible_level in reversed(range(level + 1)): 160 if possible_level in (action_sets.keys()): 161 action_set = cls.weighted_choice(action_sets[possible_level]) 162 break 163 if action_set is None: # didn't find a suitable one 164 # fall back to one from a higher level. 165 # this does not happen in valid games, but can happen in tests, when level 166 # constraints are ignored. 167 action_set, weight = list(list(action_sets.values())[0].items())[0] 168 169 return action_set
170 171 @property
172 - def name(self):
173 if hasattr(self, "_level_specific_names"): 174 return self._level_specific_names[self.level] 175 else: 176 return self._name
177