Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
349 lines (301 sloc) 13.4 KB
# ###################################################
# Copyright (C) 2008-2017 The Unknown Horizons Team
# team@unknown-horizons.org
# This file is part of Unknown Horizons.
#
# Unknown Horizons is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# ###################################################
import functools
import logging
from fife import fife
from horizons.command.uioptions import ClearTradeSlot, SetTradeSlot
from horizons.component.tradepostcomponent import TradePostComponent
from horizons.constants import STORAGE, TRADER
from horizons.extscheduler import ExtScheduler
from horizons.gui.tabs.tabinterface import TabInterface
from horizons.gui.util import create_resource_selection_dialog, get_res_icon_path, load_uh_widget
from horizons.gui.widgets.tradehistoryitem import TradeHistoryItem
from horizons.i18n import gettext as T, gettext_lazy as LazyT
from horizons.util.python.callback import Callback
from horizons.util.worldobject import WorldObject
class BuySellTab(TabInterface):
"""
Allows players to tell settlements which resources to buy or sell by adding
slots in either buy or sell mode and introducing a limit per such slot.
Also contains the trade history.
"""
log = logging.getLogger("gui")
widget = 'buysellmenu.xml'
icon_path = 'icons/tabwidget/warehouse/buysell'
buy_button_path = "content/gui/images/tabwidget/ship_to_warehouse.png"
sell_button_path = "content/gui/images/tabwidget/warehouse_to_ship.png"
dummy_icon_path = "icons/resources/none_gray"
helptext = LazyT('Trade')
def __init__(self, instance):
"""Set up the GUI and game logic for the buyselltab."""
self.inited = False # prevents execution of commands during init
# this makes sharing code easier
self.session = instance.session
self.trade_post = instance.settlement.get_component(TradePostComponent)
assert isinstance(self.trade_post, TradePostComponent)
super().__init__()
def init_widget(self):
# don't access instance beyond this point, only components
# add the buy/sell slot widgets
self.slot_widgets = {}
self.resources = None # Placeholder for resource gui
self.add_slots(len(self.trade_post.slots))
for slot_id in range(len(self.trade_post.slots)):
if self.trade_post.slots[slot_id] is not None:
trade_slot_info = self.trade_post.slots[slot_id]
self.slot_widgets[slot_id].action = 'sell' if trade_slot_info.selling else 'buy'
self.add_resource(trade_slot_info.resource_id, slot_id, trade_slot_info.limit)
if trade_slot_info.selling:
self._show_sell(self.slot_widgets[slot_id])
else:
self._show_buy(self.slot_widgets[slot_id])
# init the trade history
self.trade_history = self.widget.findChild(name='trade_history')
self.trade_history_widget_cache = {} # {(tick, player_id, resource_id, amount, gold): widget, ...}
self.hide()
self.inited = True
def hide(self):
"""Hide the tab and all widgets we may have added at runtime."""
ExtScheduler().rem_all_classinst_calls(self)
self.widget.hide()
if self.resources is not None:
self.resources.hide()
def show(self):
"""Display the tab's content, start the refresher."""
self.widget.show()
self.session.ingame_gui.minimap_to_front()
self.refresh()
ExtScheduler().add_new_object(self.refresh, self, run_in=0.4, loops=-1)
def is_visible(self):
# this tab sometimes is made up an extra widget, so it must also be considered
# when checking for visibility
return super().is_visible() or \
(self.resources is not None and self.resources.isVisible())
def _refresh_trade_history(self):
self.trade_history.removeAllChildren()
unused_rows = set(self.trade_history_widget_cache.keys())
settlement_trade_history = self.trade_post.trade_history
total_entries = len(settlement_trade_history)
for i in range(min(4, total_entries)):
row = settlement_trade_history[total_entries - i - 1]
player = WorldObject.get_object_by_id(row[1])
if row not in self.trade_history_widget_cache:
self.trade_history_widget_cache[row] = TradeHistoryItem(player, row[2], row[3], row[4])
widget = self.trade_history_widget_cache[row]
self.trade_history.addChild(widget)
unused_rows.discard(row)
self.trade_history.adaptLayout()
for row in unused_rows:
del self.trade_history_widget_cache[row]
def refresh(self):
self._refresh_trade_history()
# TODO: We don't refresh. Ticket #970
if not self.trade_post.buy_list and not self.trade_post.sell_list:
self._set_hint(T("Click on one of the resource slots to add a trade offer."))
def add_slots(self, amount):
"""
Add `amount` slot widgets to the buysellmenu.
@param amount: number of slot widgets that are to be added.
"""
content = self.widget.findChild(name="content")
for i in range(amount):
slot = load_uh_widget('trade_single_slot.xml')
self.slot_widgets[i] = slot
slot.id = i
slot.action = 'buy'
slot.res = None
slot.name = "slot_{:d}".format(i)
slot.findChild(name='button').capture(self.handle_click, event_name='mouseClicked')
slot.findChild(name='button').path = self.dummy_icon_path
slider = slot.findChild(name="slider")
slider.scale_start = 0.0
slider.scale_end = float(min(self.trade_post.get_inventory().limit, STORAGE.ITEMS_PER_TRADE_SLOT))
# Set scale according to the settlement inventory size, with a cap
slot.findChild(name="buysell").capture(Callback(self.toggle_buysell, i))
fillbar = slot.findChild(name="fillbar")
# hide fillbar by setting position
icon = slot.findChild(name="icon")
fillbar.position = (icon.width - fillbar.width - 1, icon.height)
content.addChild(slot)
self.widget.adaptLayout()
def add_resource(self, resource_id, slot_id, value=None):
"""
Adds a resource to the specified slot
@param resource_id: int - resource id
@param slot_id: int - slot number of the slot that is to be set
"""
self.log.debug("BuySellTab add_resource() resid: %s; slot_id %s; value: %s", resource_id, slot_id, value)
keep_hint = False
if self.resources is not None: # Hide resource menu
self.resources.hide()
self.show()
if resource_id != 0: # new res
self._set_hint(T("Set to buy or sell by clicking on that label, then adjust the amount via the slider to the right."))
else:
self._set_hint("")
keep_hint = True
slot = self.slot_widgets[slot_id]
slider = slot.findChild(name="slider")
if value is None: # use current slider value if player provided no input
value = int(slider.value)
else: # set slider to value entered by the player
slider.value = float(value)
if slot.action == "sell":
if slot.res is not None: # slot has been in use before, delete old value
self.clear_slot(slot_id)
if resource_id != 0:
self.set_slot_info(slot.id, resource_id, True, value)
elif slot.action == "buy":
if slot.res is not None: # slot has been in use before, delete old value
self.clear_slot(slot_id)
if resource_id != 0:
self.set_slot_info(slot.id, resource_id, False, value)
button = slot.findChild(name="button")
fillbar = slot.findChild(name="fillbar")
# reset slot value for new res
if resource_id == 0:
button.path = self.dummy_icon_path
button.helptext = ""
slot.findChild(name="amount").text = ""
slot.findChild(name="slider").value = 0.0
slot.res = None
slider.capture(None)
# hide fillbar by setting position
icon = slot.findChild(name="icon")
fillbar.position = (icon.width - fillbar.width - 1, icon.height)
button = slot.findChild(name="buysell")
button.up_image = None
button.hover_image = None
else:
icon = get_res_icon_path(resource_id)
icon_disabled = get_res_icon_path(resource_id, greyscale=True)
button.up_image = icon
button.down_image = icon
button.hover_image = icon_disabled
button.helptext = self.session.db.get_res_name(resource_id)
slot.res = resource_id
# use some python magic to assign a res attribute to the slot to
# save which resource_id it stores
slider.capture(Callback(self.slider_adjust, resource_id, slot.id))
slot.findChild(name="amount").text = "{amount:-5d}t".format(amount=value)
icon = slot.findChild(name="icon")
inventory = self.trade_post.get_inventory()
filled = (100 * inventory[resource_id]) // inventory.get_limit(resource_id)
fillbar.position = (icon.width - fillbar.width - 1,
icon.height - int(icon.height * filled))
# reuse code from toggle to finish setup (must switch state before, it will reset it)
slot.action = "sell" if slot.action == "buy" else "buy"
self.toggle_buysell(slot_id, keep_hint=keep_hint)
slot.adaptLayout()
def toggle_buysell(self, slot_id, keep_hint=False):
"""Switches modes of individual resource slots between 'buy' and 'sell'."""
slot_widget = self.slot_widgets[slot_id]
limit = int(slot_widget.findChild(name="slider").value)
if slot_widget.action == "buy":
# setting to sell
self._show_sell(slot_widget)
slot_widget.action = "sell"
elif slot_widget.action == "sell":
# setting to buy
self._show_buy(slot_widget)
slot_widget.action = "buy"
if slot_widget.res is not None:
selling = slot_widget.action == "sell"
self.set_slot_info(slot_widget.id, slot_widget.res, selling, limit)
if not keep_hint:
self._update_hint(slot_id)
def set_slot_info(self, slot_id, resource_id, selling, limit):
assert resource_id is not None
self.log.debug("BuySellTab: setting slot %d to resource %d, selling=%s, limit %d", slot_id, resource_id, selling, limit)
self.slot_widgets[slot_id].action = "sell" if selling else "buy"
if self.inited:
SetTradeSlot(self.trade_post, slot_id, resource_id, selling, limit).execute(self.session)
def clear_slot(self, slot_id):
self.log.debug("BuySellTab: Removing resource in slot %d", slot_id)
if self.inited:
ClearTradeSlot(self.trade_post, slot_id).execute(self.session)
def slider_adjust(self, resource_id, slot_id):
"""Couples the displayed limit of this slot to the slider position."""
slider = self.slot_widgets[slot_id].findChild(name="slider")
limit = int(slider.value)
self.set_slot_info(slot_id, resource_id, self.slot_widgets[slot_id].action == "sell", limit)
self.slot_widgets[slot_id].findChild(name="amount").text = "{amount:-5d}t".format(amount=limit)
self.slot_widgets[slot_id].adaptLayout()
self._update_hint(slot_id)
def handle_click(self, widget, event):
"""Handle clicks on resource slots. Left: change resource; Right: empty slot."""
if event.getButton() == fife.MouseEvent.LEFT:
self.show_resource_menu(widget.parent.id)
self.session.ingame_gui.minimap_to_front()
elif event.getButton() == fife.MouseEvent.RIGHT:
# remove the buy/sell offer
self.add_resource(0, widget.parent.id)
def show_resource_menu(self, slot_id):
"""
Displays a menu where players can choose which resource to add in the
selected slot. Available resources are all possible resources and a
'None' resource which allows to delete slot actions.
The resources are ordered by their res_id.
"""
# create dlg
buy_list = self.trade_post.buy_list
sell_list = self.trade_post.sell_list
res_filter = lambda res_id: res_id not in buy_list and res_id not in sell_list
on_click = functools.partial(self.add_resource, slot_id=slot_id)
inventory = self.trade_post.get_inventory()
self.resources = create_resource_selection_dialog(on_click, inventory,
self.session.db,
res_filter=res_filter)
self.resources.position = self.widget.position
self.hide() # hides tab that invoked the selection widget
self.session.ingame_gui.minimap_to_front()
self.resources.show() # show selection widget, still display old tab icons
def _update_hint(self, slot_id):
"""Sets default hint for last updated slot"""
slot_widget = self.slot_widgets[slot_id]
limit = int(slot_widget.findChild(name="slider").value)
action = slot_widget.action
price = self.session.db.get_res_value(slot_widget.res)
if action == "buy":
hint = T("Will buy {resource_name} for {price} gold/t whenever less than {limit}t are in stock.")
price *= TRADER.PRICE_MODIFIER_SELL
elif action == "sell":
hint = T("Will sell {resource_name} for {price} gold/t whenever more than {limit}t are available.")
price *= TRADER.PRICE_MODIFIER_BUY
hint = hint.format(limit=str(limit),
resource_name=self.session.db.get_res_name(slot_widget.res),
price=int(price))
# same price rounding as in tradepostcomponent
self._set_hint(hint)
def _set_hint(self, text):
lbl = self.widget.findChild(name="hint_label")
lbl.text = text
lbl.adaptLayout()
def _show_buy(self, slot):
"""Make slot show buy button. Purely visual change"""
button = slot.findChild(name="buysell")
button.up_image = self.buy_button_path
button.helptext = T("Buying")
def _show_sell(self, slot):
"""Make slot show sell button. Purely visual change"""
button = slot.findChild(name="buysell")
button.up_image = self.sell_button_path
button.helptext = T("Selling")
You can’t perform that action at this time.