Package horizons :: Package gui :: Package tabs :: Module boatbuildertabs
[hide private]
[frames] | no frames]

Source Code for Module horizons.gui.tabs.boatbuildertabs

  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 math 
 23  from operator import itemgetter 
 24  from typing import List, Tuple 
 25   
 26  from fife import fife 
 27  from fife.extensions.pychan.widgets import Container, HBox, Icon, Label 
 28   
 29  from horizons.command.production import AddProduction, CancelCurrentProduction, RemoveFromQueue 
 30  from horizons.constants import GAME_SPEED, PRODUCTIONLINES, RES, UNITS 
 31  from horizons.gui.util import create_resource_icon 
 32  from horizons.gui.widgets.imagebutton import CancelButton, OkButton 
 33  from horizons.i18n import gettext as T, gettext_lazy as LazyT 
 34  from horizons.scheduler import Scheduler 
 35  from horizons.util.python.callback import Callback 
 36  from horizons.world.production.producer import Producer 
 37   
 38  from .overviewtab import OverviewTab 
39 40 41 -class ProducerOverviewTabBase(OverviewTab):
42 """Base class for tabs displaying producer data.""" 43 44 @property
45 - def producer(self):
46 """The current instance's Producer compontent.""" 47 return self.instance.get_component(Producer)
48
49 50 -class UnitbuilderTabBase(ProducerOverviewTabBase):
51 """Tab Baseclass that can be used by unit builders.""" 52
53 - def show(self):
54 super().show() 55 Scheduler().add_new_object(Callback(self.refresh), self, run_in=GAME_SPEED.TICKS_PER_SECOND, loops=-1)
56
57 - def hide(self):
58 super().hide() 59 Scheduler().rem_all_classinst_calls(self)
60
61 - def refresh(self):
62 """This function is called by the TabWidget to redraw the widget.""" 63 super().refresh() 64 65 main_container = self.widget.findChild(name="UB_main_tab") 66 container_active = main_container.findChild(name="container_active") 67 container_inactive = main_container.findChild(name="container_inactive") 68 progress_container = main_container.findChild(name="UB_progress_container") 69 cancel_container = main_container.findChild(name="UB_cancel_container") 70 71 # a Unitbuilder is considered active here if it builds sth, no matter if it's paused 72 production_lines = self.producer.get_production_lines() 73 if production_lines: 74 self.show_production_is_active_container(container_active, container_inactive, 75 progress_container, cancel_container, production_lines) 76 else: 77 self.show_production_is_inactive_container(container_inactive, progress_container, 78 cancel_container, container_active) 79 self.widget.adaptLayout()
80
81 - def show_production_is_active_container(self, container_active, container_inactive, progress_container, cancel_container, production_lines):
82 """Show the container containing the active production.""" 83 container_active.parent.showChild(container_active) 84 container_inactive.parent.hideChild(container_inactive) 85 86 self.update_production_is_active_container(progress_container, container_active, cancel_container, production_lines)
87
88 - def update_production_is_active_container(self, progress_container, container_active, cancel_container, production_lines):
89 """Update the active production container.""" 90 self.update_progress(progress_container) 91 self.update_queue(container_active) 92 self.update_buttons(container_active, cancel_container) 93 94 needed_res_container = self.widget.findChild(name="UB_needed_resources_container") 95 self.update_needed_resources(needed_res_container) 96 97 # Set built unit info 98 production_line = self.producer._get_production(production_lines[0]) 99 produced_unit_id = list(production_line.get_produced_units().keys())[0] 100 101 name = self.instance.session.db.get_unit_type_name(produced_unit_id) 102 container_active.findChild(name="headline_UB_builtunit_label").text = T(name) 103 104 self.update_unit_icon(container_active, produced_unit_id) 105 106 upgrades_box = container_active.findChild(name="UB_upgrades_box") 107 upgrades_box.removeAllChildren()
108
109 - def show_production_is_inactive_container(self, container_inactive, progress_container, cancel_container, container_active):
110 """Hides all information on progress etc, and displays something to signal that the production is inactive.""" 111 container_inactive.parent.showChild(container_inactive) 112 for w in (container_active, progress_container, cancel_container): 113 w.parent.hideChild(w)
114
115 - def update_buttons(self, container_active, cancel_container):
116 """Show the correct active and inactive buttons, update cancel button""" 117 button_active = container_active.findChild(name="toggle_active_active") 118 button_inactive = container_active.findChild(name="toggle_active_inactive") 119 to_active = not self.producer.is_active() 120 121 if not to_active: # swap what we want to show and hide 122 button_active, button_inactive = button_inactive, button_active 123 button_active.parent.hideChild(button_active) 124 125 button_inactive.parent.showChild(button_inactive) 126 127 set_active_cb = Callback(self.producer.set_active, active=to_active) 128 button_inactive.capture(set_active_cb, event_name="mouseClicked") 129 130 cancel_container.parent.showChild(cancel_container) 131 cancel_button = self.widget.findChild(name="UB_cancel_button") 132 cancel_cb = Callback(CancelCurrentProduction(self.producer).execute, self.instance.session) 133 cancel_button.capture(cancel_cb, event_name="mouseClicked")
134
135 - def update_unit_icon(self, container_active, produced_unit_id):
136 """Update the icon displaying the unit that is being built.""" 137 unit_icon = container_active.findChild(name="UB_cur_unit_icon") 138 unit_icon.helptext = self.instance.session.db.get_unit_tooltip(produced_unit_id) 139 unit_icon.image = self.__class__.UNIT_PREVIEW_IMAGE.format(type_id=produced_unit_id)
140
141 - def update_queue(self, container_active):
142 """ Update the queue display""" 143 queue = self.producer.get_unit_production_queue() 144 queue_container = container_active.findChild(name="queue_container") 145 queue_container.removeAllChildren() 146 for place_in_queue, unit_type in enumerate(queue): 147 image = self.__class__.UNIT_THUMBNAIL.format(type_id=unit_type) 148 helptext = T("{ship} (place in queue: {place})").format( 149 ship=self.instance.session.db.get_unit_type_name(unit_type), 150 place=place_in_queue + 1) 151 # people don't count properly, always starting at 1.. 152 icon_name = "queue_elem_" + str(place_in_queue) 153 154 try: 155 icon = Icon(name=icon_name, image=image, helptext=helptext) 156 except fife.NotFound as e: 157 # It's possible that this error was raised from a missing thumbnail asset, 158 # so we check against that now and use a fallback thumbnail instead 159 if 'content/gui/icons/thumbnails/' in e.what(): 160 # actually load the fallback unit image 161 image = self.__class__.UNIT_THUMBNAIL.format(type_id="unknown_unit") 162 icon = Icon(name=icon_name, image=image, helptext=helptext) 163 else: 164 raise 165 166 rm_from_queue_cb = Callback(RemoveFromQueue(self.producer, place_in_queue).execute, 167 self.instance.session) 168 icon.capture(rm_from_queue_cb, event_name="mouseClicked") 169 queue_container.addChild(icon)
170
171 - def update_needed_resources(self, needed_res_container):
172 """ Update needed resources """ 173 production = self.producer.get_productions()[0] 174 needed_res = production.get_consumed_resources() 175 # Now sort! -amount is the positive value, drop unnecessary res (amount 0) 176 needed_res = {res: -amount for res, amount in needed_res.items() if amount < 0} 177 needed_res = sorted(needed_res.items(), key=itemgetter(1), reverse=True) 178 needed_res_container.removeAllChildren() 179 for i, (res, amount) in enumerate(needed_res): 180 icon = create_resource_icon(res, self.instance.session.db) 181 icon.max_size = icon.min_size = icon.size = (16, 16) 182 label = Label(name="needed_res_lbl_{}".format(i)) 183 label.text = '{amount}t'.format(amount=amount) 184 new_hbox = HBox(name="needed_res_box_{}".format(i)) 185 new_hbox.addChildren(icon, label) 186 needed_res_container.addChild(new_hbox)
187
188 - def update_progress(self, progress_container):
189 """Update displayed progress""" 190 progress_container.parent.showChild(progress_container) 191 progress = math.floor(self.producer.get_production_progress() * 100) 192 self.widget.findChild(name='progress').progress = progress 193 progress_perc = self.widget.findChild(name='UB_progress_perc') 194 progress_perc.text = '{progress}%'.format(progress=progress)
195
196 197 -class BoatbuilderTab(UnitbuilderTabBase):
198 widget = 'boatbuilder.xml' 199 helptext = LazyT("Boat builder overview") 200 201 UNIT_THUMBNAIL = "content/gui/icons/thumbnails/{type_id}.png" 202 UNIT_PREVIEW_IMAGE = "content/gui/images/objects/ships/116/{type_id}.png"
203
204 # this tab additionally requests functions for: 205 # * decide: show [start view] = nothing but info text, look up the xml, or [building status view] 206 # * get: currently built ship: name / image / upgrades 207 # * resources still needed: 208 # (a) which ones? three most important (image, name) 209 # (b) how many? sort by amount, display (amount, overall amount needed of them, image) 210 # * pause production (keep order and "running" running costs [...] but collect no new resources) 211 # * abort building process: delete task, remove all resources, display [start view] again 212 213 214 -class BoatbuilderSelectTab(ProducerOverviewTabBase):
215 widget = 'boatbuilder_showcase.xml' 216
217 - def init_widget(self):
218 super().init_widget() 219 self.widget.findChild(name='headline').text = self.helptext 220 221 showcases = self.widget.findChild(name='showcases') 222 for i, (ship, prodline) in enumerate(self.ships): 223 showcase = self.build_ship_info(i, ship, prodline) 224 showcases.addChild(showcase)
225
226 - def build_ship_info(self, index, ship, prodline):
227 size = (260, 90) 228 widget = Container(name='showcase_{}'.format(index), position=(0, 20 + index * 90), 229 min_size=size, max_size=size, size=size) 230 bg_icon = Icon(image='content/gui/images/background/square_80.png', name='bg_{}'.format(index)) 231 widget.addChild(bg_icon) 232 233 image = 'content/gui/images/objects/ships/76/{unit_id}.png'.format(unit_id=ship) 234 helptext = self.instance.session.db.get_unit_tooltip(ship) 235 unit_icon = Icon(image=image, name='icon_{}'.format(index), position=(2, 2), 236 helptext=helptext) 237 widget.addChild(unit_icon) 238 239 # if not buildable, this returns string with reason why to be displayed as helptext 240 #ship_unbuildable = self.is_ship_unbuildable(ship) 241 ship_unbuildable = False 242 if not ship_unbuildable: 243 button = OkButton(position=(60, 50), name='ok_{}'.format(index), helptext=T('Build this ship!')) 244 button.capture(Callback(self.start_production, prodline)) 245 else: 246 button = CancelButton(position=(60, 50), name='ok_{}'.format(index), 247 helptext=ship_unbuildable) 248 249 widget.addChild(button) 250 251 # Get production line info 252 production = self.producer.create_production_line(prodline) 253 # consumed == negative, reverse to sort in *ascending* order: 254 costs = sorted(production.consumed_res.items(), key=itemgetter(1)) 255 for i, (res, amount) in enumerate(costs): 256 xoffset = 103 + (i % 2) * 55 257 yoffset = 20 + (i // 2) * 20 258 icon = create_resource_icon(res, self.instance.session.db) 259 icon.max_size = icon.min_size = icon.size = (16, 16) 260 icon.position = (xoffset, yoffset) 261 label = Label(name='cost_{}_{}'.format(index, i)) 262 if res == RES.GOLD: 263 label.text = str(-amount) 264 else: 265 label.text = '{amount:02}t'.format(amount=-amount) 266 label.position = (22 + xoffset, yoffset) 267 widget.addChild(icon) 268 widget.addChild(label) 269 return widget
270
271 - def start_production(self, prod_line_id):
272 AddProduction(self.producer, prod_line_id).execute(self.instance.session) 273 # show overview tab 274 self.instance.session.ingame_gui.get_cur_menu().show_tab(0)
275
276 277 -class BoatbuilderFisherTab(BoatbuilderSelectTab):
278 icon_path = 'icons/tabwidget/boatbuilder/fisher' 279 helptext = LazyT("Fisher boats") 280 281 ships = [ 282 #(UNITS.FISHER_BOAT, PRODUCTIONLINES.FISHING_BOAT), 283 #(UNITS.CUTTER, PRODUCTIONLINES.CUTTER), 284 #(UNITS.HERRING_FISHER, PRODUCTIONLINES.HERRING_FISHER), 285 #(UNITS.WHALER, PRODUCTIONLINES.WHALER), 286 ] # type: List[Tuple[int, int]]
287
288 289 -class BoatbuilderTradeTab(BoatbuilderSelectTab):
290 icon_path = 'icons/tabwidget/boatbuilder/trade' 291 helptext = LazyT("Trade boats") 292 293 ships = [ 294 (UNITS.HUKER_SHIP, PRODUCTIONLINES.HUKER), 295 #(UNITS.COURIER_BOAT, PRODUCTIONLINES.COURIER_BOAT), 296 #(UNITS.SMALL_MERCHANT, PRODUCTIONLINES.SMALL_MERCHANT), 297 #(UNITS.BIG_MERCHANT, PRODUCTIONLINES.BIG_MERCHANT), 298 ]
299
300 301 -class BoatbuilderWar1Tab(BoatbuilderSelectTab):
302 icon_path = 'icons/tabwidget/boatbuilder/war1' 303 helptext = LazyT("War boats") 304 305 ships = [ 306 #(UNITS.SMALL_GUNBOAT, PRODUCTIONLINES.SMALL_GUNBOAT), 307 #(UNITS.NAVAL_CUTTER, PRODUCTIONLINES.NAVAL_CUTTER), 308 #(UNITS.BOMBADIERE, PRODUCTIONLINES.BOMBADIERE), 309 #(UNITS.SLOOP_O_WAR, PRODUCTIONLINES.SLOOP_O_WAR), 310 ] # type: List[Tuple[int, int]]
311
312 313 -class BoatbuilderWar2Tab(BoatbuilderSelectTab):
314 icon_path = 'icons/tabwidget/boatbuilder/war2' 315 helptext = LazyT("War ships") 316 317 ships = [ 318 #(UNITS.GALLEY, PRODUCTIONLINES.GALLEY), 319 #(UNITS.BIG_GUNBOAT, PRODUCTIONLINES.BIG_GUNBOAT), 320 #(UNITS.CORVETTE, PRODUCTIONLINES.CORVETTE), 321 (UNITS.FRIGATE, PRODUCTIONLINES.FRIGATE), 322 ]
323
324 325 # these tabs additionally request functions for: 326 # * goto: show [confirm view] tab (not accessible via tab button in the end) 327 # need to provide information about the selected ship (which of the 4 buttons clicked) 328 # * check: mark those ship's buttons as unbuildable (close graphics) which do not meet the specified requirements. 329 # the tooltips contain this info as well. 330 331 -class BoatbuilderConfirmTab(ProducerOverviewTabBase):
332 widget = 'boatbuilder_confirm.xml' 333 helptext = LazyT("Confirm order") 334
335 - def init_widget(self):
336 super().init_widget() 337 events = {'create_unit': self.start_production} 338 self.widget.mapEvents(events)
339
340 - def start_production(self):
341 AddProduction(self.producer, 15).execute(self.instance.session)
342 343 # this "tab" additionally requests functions for: 344 # * get: currently ordered ship: name / image / type (fisher/trade/war) 345 # * => get: currently ordered ship: description text / costs / available upgrades 346 # (fisher/trade/war, builder level) 347 # * if resource icons not hardcoded: resource icons, sort them by amount 348 # UPGRADES: * checkboxes * check for boat builder level (+ research) * add. costs (get, add, display) 349 # * def start_production(self): <<< actually start to produce the selected ship unit with the selected upgrades 350 # (use inventory or go collect resources, switch focus to overview tab). 351 # IMPORTANT: lock this button until unit is actually produced (no queue!) 352