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

Source Code for Module horizons.world.player

  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 collections 
 23  from typing import Any, Dict, Sequence, Union 
 24   
 25  import horizons.main 
 26  from horizons.component.componentholder import ComponentHolder 
 27  from horizons.component.storagecomponent import StorageComponent 
 28  from horizons.component.tradepostcomponent import TradePostComponent 
 29  from horizons.constants import PLAYER 
 30  from horizons.messaging import PlayerInventoryUpdated, PlayerLevelUpgrade, SettlerUpdate 
 31  from horizons.scenario import CONDITIONS 
 32  from horizons.scheduler import Scheduler 
 33  from horizons.util.color import Color 
 34  from horizons.util.difficultysettings import DifficultySettings 
 35  from horizons.util.inventorychecker import InventoryChecker 
 36  from horizons.util.python import decorators 
 37  from horizons.util.worldobject import WorldObject 
 38  from horizons.world.playerstats import PlayerStats 
39 40 41 -class Player(ComponentHolder, WorldObject):
42 """Class representing a player""" 43 44 STATS_UPDATE_INTERVAL = 3 # seconds 45 46 regular_player = True # either a human player or a normal AI player (not trader or pirate) 47 component_templates = ({'StorageComponent': {'PositiveStorage': {}}},) # type: Sequence[Union[str, Dict[str, Any]]] 48
49 - def __init__(self, session, worldid, name, color, clientid=None, difficulty_level=None):
50 """ 51 @type session: horizons.session.Session 52 @param session: Session instance 53 @param worldid: player's worldid 54 @param name: user-chosen name 55 @param color: color of player (as Color) 56 @param clientid: id of client 57 @param inventory: {res: value} that are put in the players inventory 58 """ 59 assert isinstance(session, horizons.session.Session) 60 self.session = session 61 super().__init__(worldid=worldid) 62 self.__init(name, color, clientid, difficulty_level, 0)
63
64 - def initialize(self, inventory):
65 super().initialize() 66 if inventory: 67 for res, value in inventory.items(): 68 self.get_component(StorageComponent).inventory.alter(res, value)
69
70 - def __init(self, name, color, clientid, difficulty_level, max_tier_notification, settlerlevel=0):
71 assert isinstance(color, Color) 72 assert isinstance(name, str) and name 73 try: 74 self.name = str(name) 75 except UnicodeDecodeError: 76 # WORKAROUND: this line should be the only unicode conversion here. 77 # however, if unicode() gets a parameter, it will fail if the string is already unicode. 78 self.name = str(name, errors='ignore') 79 self.color = color 80 self.clientid = clientid 81 self.difficulty = DifficultySettings.get_settings(difficulty_level) 82 self.max_tier_notification = max_tier_notification 83 self.settler_level = settlerlevel 84 self._stats = None 85 assert self.color.is_default_color, "Player color has to be a default color" 86 87 if self.regular_player: 88 SettlerUpdate.subscribe(self.notify_settler_reached_level)
89 90 @property
91 - def is_local_player(self):
92 return self is self.session.world.player
93
94 - def get_latest_stats(self):
95 if self._stats is None or self._stats.collection_tick + PLAYER.STATS_UPDATE_FREQUENCY < Scheduler().cur_tick: 96 self._stats = PlayerStats(self) 97 return self._stats
98 99 @property
100 - def settlements(self):
101 """Calculate settlements dynamically to save having a redundant list here""" 102 return [settlement for settlement in self.session.world.settlements if 103 settlement.owner == self]
104
105 - def save(self, db):
106 super().save(db) 107 client_id = None 108 if self.clientid is not None or self is self.session.world.player: 109 client_id = self.clientid 110 db("INSERT INTO player" 111 " (rowid, name, color, client_id, settler_level," 112 " difficulty_level, max_tier_notification)" 113 " VALUES(?, ?, ?, ?, ?, ?, ?)", 114 self.worldid, self.name, self.color.id, client_id, self.settler_level, 115 self.difficulty.level if self.difficulty is not None else None, 116 self.max_tier_notification)
117 118 @classmethod
119 - def load(cls, session, db, worldid):
120 self = cls.__new__(cls) 121 self.session = session 122 self._load(db, worldid) 123 return self
124
125 - def _load(self, db, worldid):
126 """This function makes it possible to load playerdata into an already allocated 127 Player instance, which is used e.g. in Trader.load""" 128 super().load(db, worldid) 129 130 color, name, client_id, settlerlevel, difficulty_level, max_tier_notification = db( 131 "SELECT color, name, client_id, settler_level, difficulty_level, max_tier_notification" 132 " FROM player WHERE rowid = ?", worldid)[0] 133 self.__init(name, Color.get(color), client_id, difficulty_level, max_tier_notification, settlerlevel=settlerlevel)
134
135 - def notify_settler_reached_level(self, message):
136 """Settler calls this to notify the player.""" 137 if message.sender.owner is not self: 138 return 139 if message.level > self.settler_level: 140 self.settler_level = message.level 141 self.session.scenario_eventhandler.check_events(CONDITIONS.settler_level_greater) 142 for settlement in self.settlements: 143 settlement.level_upgrade(self.settler_level) 144 self._changed() 145 PlayerLevelUpgrade.broadcast(self, self.settler_level, message.sender)
146
147 - def end(self):
148 self._stats = None 149 self.session = None 150 151 if self.regular_player: 152 SettlerUpdate.unsubscribe(self.notify_settler_reached_level)
153 154 @decorators.temporary_cachedmethod(timeout=STATS_UPDATE_INTERVAL)
155 - def get_balance_estimation(self):
156 """This takes a while to calculate, so only do it every 2 seconds at most""" 157 return sum(settlement.balance for settlement in self.settlements)
158 159 @decorators.temporary_cachedmethod(timeout=STATS_UPDATE_INTERVAL)
160 - def get_statistics(self):
161 """Returns a namedtuple containing player-wide statistics""" 162 Data = collections.namedtuple('Data', ['running_costs', 'taxes', 'sell_income', 'buy_expenses', 'balance']) 163 # balance is duplicated here and above such that the version above 164 # can be used independently and the one here is always perfectly in sync 165 # with the other values here 166 167 get_sum = lambda l, attr: sum(getattr(obj, attr) for obj in l) 168 trade_posts = [s.get_component(TradePostComponent) for s in self.settlements] 169 return Data( 170 running_costs=get_sum(self.settlements, 'cumulative_running_costs'), 171 taxes=get_sum(self.settlements, 'cumulative_taxes'), 172 sell_income=get_sum(trade_posts, 'sell_income'), 173 buy_expenses=get_sum(trade_posts, 'buy_expenses'), 174 balance=get_sum(self.settlements, 'balance'), 175 )
176
177 178 -class HumanPlayer(Player):
179 """Class for players that physically sit in front of the machine where the game is run""" 180
181 - def __init(self, *args, **kwargs):
182 super().__init(*args, **kwargs) 183 self.__inventory_checker = InventoryChecker(PlayerInventoryUpdated, self.get_component(StorageComponent), 4)
184
185 - def end(self):
186 if hasattr(self, '__inventory_checker'): 187 self.__inventory_checker.remove() 188 super().end()
189