NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

sellSkinDialogue.py (21060B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\collection\sellSkinDialogue.py
      2 import eveicon
      3 import math
      4 import uthread2
      5 from carbonui import Align, AxisAlignment, ButtonStyle, ButtonVariant, Density, uiconst
      6 from carbonui import TextColor
      7 from carbonui.text.styles import TextHeader, TextBody, TextDetail
      8 from carbonui.button.group import ButtonGroup, ButtonSizeMode
      9 from carbonui.control.button import Button
     10 from carbonui.control.combo import Combo
     11 from carbonui.control.comboEntryData import ComboEntryData
     12 from carbonui.control.singlelineedits.singleLineEditInteger import SingleLineEditInteger
     13 from carbonui.control.window import Window
     14 from carbonui.decorative.divider_line import DividerLine
     15 from carbonui.primitives.container import Container
     16 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     17 from carbonui.primitives.sprite import Sprite
     18 from cosmetics.client.ships.skins.errors import get_listing_error_text
     19 from cosmetics.client.shipSkinTradingSvc import get_ship_skin_trading_svc
     20 from cosmetics.common.ships.skins.util import Currency
     21 from eve.client.script.ui import eveColor
     22 from player_messaging.client.ui_message import message_player
     23 from eve.client.script.ui.cosmetics.ship import tradingUtil
     24 from eve.client.script.ui.cosmetics.ship.controls.buyerFee import SellSKINBuyerFee
     25 from eve.client.script.ui.cosmetics.ship.tradingUtil import SellAvailability
     26 from eve.client.script.ui.cosmetics.ship.pages.store.tooltipInfoIcons import HubTaxRateSkillsInfoIcon
     27 from eve.client.script.ui.shared.market.singleLineEditMarketPrice import SingleLineEditMarketPrice
     28 from eve.common.script.util.eveFormat import FmtAmt, FmtISK
     29 from eveui.autocomplete.player.field import CharOrCorpOrAllianceField
     30 from localization import GetByLabel
     31 
     32 class SellSkinDialogue(Window):
     33     __notifyevents__ = ['OnSessionChanged']
     34     default_windowID = 'SellSkinDialogue'
     35     default_iconNum = 'res:/UI/Texture/WindowIcons/paint_tool.png'
     36     default_caption = GetByLabel('UI/Personalization/ShipSkins/SKINR/SellSkin')
     37     default_fixedWidth = 515
     38     default_fixedHeight = 480
     39     default_isMinimizable = False
     40     default_isStackable = False
     41     default_isCollapseable = False
     42 
     43     def __init__(self, skin_license, *args, **kwargs):
     44         super(SellSkinDialogue, self).__init__(*args, **kwargs)
     45         self.skin_license = skin_license
     46         self.is_layout_done = False
     47         self.update_thread = None
     48         self.sell_skin_thread = None
     49         self._tax_rate = None
     50         self._broker_fee = None
     51         self._buyer_fee_amount = None
     52         self.construct_layout()
     53         sm.RegisterNotify(self)
     54 
     55     def Close(self, setClosed = False, *args, **kwargs):
     56         try:
     57             self.kill_threads()
     58             sm.UnregisterNotify(self)
     59         finally:
     60             super(SellSkinDialogue, self).Close(setClosed, *args, **kwargs)
     61 
     62     def kill_threads(self):
     63         self.kill_update_thread()
     64         self.kill_sell_skin_thread()
     65 
     66     def kill_update_thread(self):
     67         if self.update_thread is not None:
     68             self.update_thread.kill()
     69             self.update_thread = None
     70 
     71     def kill_sell_skin_thread(self):
     72         if self.sell_skin_thread is not None:
     73             self.sell_skin_thread.kill()
     74             self.sell_skin_thread = None
     75 
     76     def construct_layout(self):
     77         self.content.padding = (32, 32, 32, 32)
     78         self.construct_duration()
     79         self.construct_availability()
     80         self.construct_search()
     81         self.construct_listing()
     82         DividerLine(parent=self.content, align=Align.TOTOP, padTop=32)
     83         self.construct_buyer_fee()
     84         self.construct_service_fee()
     85         self.construct_listing_cost()
     86         self.construct_profit()
     87         DividerLine(parent=self.content, align=Align.TOTOP, padTop=16)
     88         self.construct_buttons()
     89         self.is_layout_done = True
     90         self.update_async()
     91 
     92     def construct_duration(self):
     93         duration_container = Container(name='duration_container', parent=self.content, align=Align.TOTOP, height=32)
     94         TextHeader(name='duration_label', parent=duration_container, align=Align.CENTERLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Collection/SellListingDuration'), bold=True)
     95         options = tradingUtil.get_sell_duration_options()
     96         self.duration_combo = Combo(name='duration_combo', parent=duration_container, align=Align.TORIGHT, options=options, select=options[-1], callback=self.on_input_change, maxVisibleEntries=len(options), width=160)
     97 
     98     def construct_listing(self):
     99         self.listing_container = Container(name='listing_container', parent=self.content, align=Align.TOTOP, height=32, padTop=16)
    100         self.construct_copies()
    101         self.construct_amount()
    102         self.construct_currency()
    103 
    104     def construct_availability(self):
    105         availability_container = Container(name='availability_container', parent=self.content, align=Align.TOTOP, height=32, padTop=16)
    106         TextHeader(name='availability_label', parent=availability_container, align=Align.CENTERLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/ListingAvailability'), bold=True)
    107         options = tradingUtil.get_sell_availability_options()
    108         self.availability_combo = Combo(name='availability_combo', parent=availability_container, align=Align.TORIGHT, options=options, select=options[-1], callback=self.on_availability_change, maxVisibleEntries=len(options), width=160)
    109 
    110     def construct_search(self):
    111         self.search_container = Container(name='search_container', parent=self.content, align=Align.TOTOP, height=52, padTop=8, display=False)
    112         header_container = Container(name='header_container', parent=self.search_container, align=Align.TOTOP, height=16)
    113         Sprite(name='icon', parent=header_container, align=Align.TOLEFT, texturePath=eveicon.pilot_or_organization, color=TextColor.SECONDARY, pos=(0, 0, 16, 16))
    114         TextDetail(name='label', parent=header_container, align=Align.TOLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Collection/SellTargetSearchHeader'), color=TextColor.SECONDARY, padLeft=4)
    115         self.search_field = CharOrCorpOrAllianceField(name='search_field', parent=self.search_container, align=Align.TOTOP, placeholder=GetByLabel('UI/Personalization/ShipSkins/SKINR/Collection/SellTargetSearchPlaceholder'), show_suggestions_on_focus=True, padTop=4)
    116         self.search_field.autocomplete_controller.on_fetch_start.connect(self.on_search_fetch_start)
    117         self.search_field.autocomplete_controller.on_completed.connect(self.on_search_completed)
    118 
    119     def on_search_fetch_start(self, *args):
    120         self.update_async()
    121 
    122     def on_search_completed(self, *args):
    123         self.update_async()
    124 
    125     def construct_copies(self):
    126         copies_container = ContainerAutoSize(name='copies_container', parent=self.listing_container, align=Align.TOLEFT, height=32)
    127         TextHeader(name='copies_label', parent=copies_container, align=Align.TOLEFT, text=GetByLabel('UI/Industry/Copies'), bold=True, padTop=4)
    128         self.copies_edit = SingleLineEditInteger(name='copies_edit', parent=copies_container, align=Align.TOLEFT, density=Density.EXPANDED, width=56, minValue=1, maxValue=self.skin_license.nb_unactivated, setvalue=1, OnChange=self.on_input_change, padLeft=10)
    129 
    130     def construct_amount(self):
    131         self.amount_container = ContainerAutoSize(name='amount_container', parent=self.listing_container, align=Align.TORIGHT, height=32, padLeft=10)
    132         self.amount_edit = SingleLineEditMarketPrice(name='amount_edit', parent=self.amount_container, align=Align.TOLEFT, density=Density.EXPANDED, minValue=1, setvalue=10, decimalPlaces=0, OnChange=self.on_input_change, width=160, isTickActivated=False)
    133 
    134     def construct_currency(self):
    135         currency_container = ContainerAutoSize(name='currency_container', parent=self.listing_container, align=Align.TORIGHT, height=32)
    136         TextHeader(name='currency_label', parent=currency_container, align=Align.TOLEFT, text=GetByLabel('UI/Market/Marketbase/Price'), bold=True, padTop=4)
    137         options = [ComboEntryData(label=GetByLabel('UI/Common/ISK'), returnValue=Currency.ISK, icon=eveicon.isk), ComboEntryData(label=GetByLabel('UI/Common/PLEX'), returnValue=Currency.PLEX, icon=eveicon.plex)]
    138         self.currency_combo = Combo(name='currency_combo', parent=currency_container, align=Align.TOLEFT, options=options, select=Currency.PLEX, callback=self.on_currency_change, maxVisibleEntries=len(options), width=56, padLeft=10)
    139 
    140     def construct_buyer_fee(self):
    141         self.buyer_fee = SellSKINBuyerFee(name='buyer_fee', parent=self.content, align=Align.TOTOP, padTop=24, display=False)
    142 
    143     def construct_service_fee(self):
    144         self.service_fee_container = Container(name='service_fee_container', parent=self.content, align=Align.TOTOP, height=18, padTop=20)
    145         label_container = ContainerAutoSize(name='label_container', parent=self.service_fee_container, align=Align.TOLEFT, state=uiconst.UI_NORMAL, hint=GetByLabel('UI/Personalization/ShipSkins/SKINR/ServiceFeeHint'))
    146         TextBody(name='service_fee_label', parent=label_container, align=Align.TOLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/ServiceFee'), color=TextColor.SECONDARY)
    147         HubTaxRateSkillsInfoIcon(parent=ContainerAutoSize(parent=label_container, align=Align.TOLEFT, padLeft=4), align=Align.CENTERLEFT)
    148         amount_container = ContainerAutoSize(name='amount_container', parent=self.service_fee_container, align=Align.TORIGHT, state=uiconst.UI_NORMAL, hint=GetByLabel('UI/Personalization/ShipSkins/SKINR/ServiceFeeHint'))
    149         self.service_fee_amount_label = TextBody(name='service_fee_amount_label', parent=amount_container, align=Align.TOLEFT, color=TextColor.SECONDARY)
    150 
    151     def construct_listing_cost(self):
    152         listing_cost_container = Container(name='listing_cost_container', parent=self.content, align=Align.TOTOP, height=18, padTop=8)
    153         label_container = ContainerAutoSize(name='label_container', parent=listing_cost_container, align=Align.TOLEFT, state=uiconst.UI_NORMAL, hint=GetByLabel('UI/Personalization/ShipSkins/SKINR/ListingCostHint'))
    154         TextBody(name='listing_cost_label', parent=label_container, align=Align.TOLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/ListingCost'), color=TextColor.SECONDARY)
    155         amount_container = ContainerAutoSize(name='amount_container', parent=listing_cost_container, align=Align.TORIGHT, state=uiconst.UI_NORMAL, hint=GetByLabel('UI/Personalization/ShipSkins/SKINR/ListingCostHint'))
    156         self.listing_cost_amount_label = TextBody(name='listing_cost_amount_label', parent=amount_container, align=Align.TOLEFT, color=TextColor.SECONDARY, padLeft=4)
    157 
    158     def construct_profit(self):
    159         profit_container = Container(name='profit_container', parent=self.content, align=Align.TOTOP, height=28, padTop=16)
    160         TextHeader(name='profit_label', parent=profit_container, align=Align.TOLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/TotalProfit'), bold=True)
    161         self.profit_amount_label = TextHeader(name='profit_amount_label', parent=profit_container, align=Align.TORIGHT, bold=True, padLeft=4)
    162         currency_icon_container = Container(name='currency_icon_container', parent=profit_container, align=Align.TORIGHT, width=20)
    163         self.currency_icon = Sprite(name='currency_icon', parent=currency_icon_container, align=Align.CENTER, pos=(0, 0, 20, 20), padBottom=4)
    164 
    165     def construct_buttons(self):
    166         button_group = ButtonGroup(name='button_group', parent=self.content, align=Align.TOBOTTOM, button_alignment=AxisAlignment.CENTER, density=Density.EXPANDED, button_size_mode=ButtonSizeMode.STRETCH)
    167         self.sell_button = Button(name='sell_button', label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Collection/Sell'), variant=ButtonVariant.PRIMARY, func=self.on_sell_button)
    168         button_group.add_button(self.sell_button)
    169         self.cancel_button = Button(name='cancel_button', label=GetByLabel('UI/Common/Buttons/Cancel'), variant=ButtonVariant.GHOST, func=self.on_cancel_button)
    170         button_group.add_button(self.cancel_button)
    171 
    172     def on_input_change(self, *args):
    173         self.update_async()
    174 
    175     def on_availability_change(self, combobox, key, value):
    176         self.search_container.display = value == SellAvailability.SPECIFIC
    177         self.update_async()
    178 
    179     def on_currency_change(self, *args):
    180         self.update_amount_edit()
    181         self.update_async()
    182 
    183     @uthread2.debounce(wait=0.2)
    184     def update_async(self):
    185         if not self.is_layout_done:
    186             return
    187         self.kill_update_thread()
    188         self.update_thread = uthread2.start_tasklet(self.update)
    189 
    190     def update(self):
    191         self.update_fee_and_tax_values()
    192         self.update_buyer_fee()
    193         self.update_service_fee()
    194         self.update_listing_cost()
    195         self.update_profit()
    196         self.update_currency()
    197         self.update_buttons()
    198         self.update_size()
    199 
    200     def update_fee_and_tax_values(self):
    201         self._tax_rate = self.get_tax_rate()
    202         self._broker_fee = self.get_broker_fee()
    203         self._buyer_fee_amount = self.get_buyer_fee_amount()
    204 
    205     def update_buyer_fee(self):
    206         self.buyer_fee.amount = self.buyer_fee_amount
    207 
    208     def update_service_fee(self):
    209         if self.tax_rate is None:
    210             self.service_fee_amount_label.text = None
    211             return
    212         self.service_fee_amount_label.text = '{tax}%'.format(tax=self.tax_rate)
    213         self.service_fee_amount_label.color = eveColor.SUCCESS_GREEN if self.tax_rate == 0 else TextColor.SECONDARY
    214 
    215     def update_listing_cost(self):
    216         if self.broker_fee is None:
    217             self.listing_cost_amount_label.text = None
    218             return
    219         self.listing_cost_amount_label.text = FmtISK(-self.broker_fee)
    220         self.listing_cost_amount_label.color = eveColor.SUCCESS_GREEN if self.broker_fee == 0 else TextColor.SECONDARY
    221 
    222     def update_profit(self):
    223         if self.tax_rate is None or self.broker_fee is None:
    224             self.profit_amount_label.text = None
    225             return
    226         tax_rate_percent = self.tax_rate / 100.0
    227         if self.currency == Currency.PLEX:
    228             profit = math.floor(self.price - self.price * tax_rate_percent)
    229             self.profit_amount_label.text = FmtAmt(profit)
    230         else:
    231             profit = self.price - self.price * tax_rate_percent - self.broker_fee
    232             self.profit_amount_label.text = FmtISK(profit)
    233         self.profit_amount_label.color = self.get_profit_text_color(profit)
    234 
    235     def get_profit_text_color(self, profit):
    236         if profit > 0:
    237             return TextColor.SUCCESS
    238         elif profit < 0:
    239             return TextColor.DANGER
    240         else:
    241             return TextColor.NORMAL
    242 
    243     def update_currency(self):
    244         if self.currency == Currency.ISK:
    245             self.currency_icon.texturePath = eveicon.isk
    246             self.currency_icon.color = eveColor.WHITE
    247         elif self.currency == Currency.PLEX:
    248             self.currency_icon.texturePath = eveicon.plex
    249             self.currency_icon.color = eveColor.PLEX_YELLOW
    250 
    251     def update_availabilities(self):
    252         select = self.availability_combo.selectedValue
    253         options = tradingUtil.get_sell_availability_options()
    254         self.availability_combo.LoadOptions(options, select)
    255         self.search_container.display = self.availability_combo.selectedValue == SellAvailability.SPECIFIC
    256 
    257     def update_amount_edit(self):
    258         value = self.amount
    259         if self.currency == Currency.ISK:
    260             self.amount_edit.decimalPlaces = 2
    261             self.amount_edit.ActivateTickAlignment()
    262         elif self.currency == Currency.PLEX:
    263             self.amount_edit.decimalPlaces = 0
    264             self.amount_edit.DeactivateTickAlignment()
    265         self.amount_edit.SetValue(value, docallback=False)
    266 
    267     def update_buttons(self):
    268         if self.is_valid:
    269             self.sell_button.style = ButtonStyle.NORMAL
    270             self.sell_button.hint = None
    271             return
    272         self.sell_button.style = ButtonStyle.DANGER
    273         if not self.has_sufficient_funds:
    274             self.sell_button.hint = GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/ListingErrors/InsufficientISKFunds')
    275         elif not self.has_valid_target:
    276             self.sell_button.hint = GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/ListingErrors/InvalidListingTarget')
    277 
    278     def update_size(self):
    279         if self.buyer_fee_amount:
    280             if self.sell_availability == SellAvailability.SPECIFIC:
    281                 self.SetFixedHeight(610)
    282             else:
    283                 self.SetFixedHeight(550)
    284         elif self.sell_availability == SellAvailability.SPECIFIC:
    285             self.SetFixedHeight(540)
    286         else:
    287             self.SetFixedHeight(self.default_fixedHeight)
    288 
    289     def on_sell_button(self, *args):
    290         if self.is_valid:
    291             self.sell_skin_async()
    292 
    293     def sell_skin_async(self):
    294         self.kill_sell_skin_thread()
    295         self.sell_skin_thread = uthread2.StartTasklet(self.sell_skin)
    296 
    297     def sell_skin(self):
    298         self.sell_button.busy = True
    299         self.sell_button.disabled = True
    300         try:
    301             svc = get_ship_skin_trading_svc()
    302             error = svc.create_listing(currency=self.currency, price=int(self.amount), skin_hex=self.skin_license.skin_hex, duration_in_seconds=int(self.duration), quantity=int(self.copies), target_id=self.sell_target_type_id)
    303             if error:
    304                 message_player(GetByLabel(get_listing_error_text(error)))
    305             else:
    306                 self.SetModalResult(uiconst.ID_OK)
    307         except Exception as e:
    308             raise RuntimeError('Unsupported error occurred when creating skin listing: %s' % e)
    309         finally:
    310             self.sell_skin_thread = None
    311             self.sell_button.busy = False
    312             self.sell_button.disabled = False
    313 
    314     def get_tax_rate(self):
    315         tax_rate, error = get_ship_skin_trading_svc().get_tax_rate(target_id=self.sell_target_type_id)
    316         if error:
    317             self.show_error_message(error)
    318             return None
    319         else:
    320             return tax_rate
    321 
    322     def get_broker_fee(self):
    323         broker_fees, error = get_ship_skin_trading_svc().get_broker_fees_per_duration(target_id=self.sell_target_type_id)
    324         if error:
    325             self.show_error_message(error)
    326             return None
    327         else:
    328             return broker_fees[self.duration]
    329 
    330     def get_buyer_fee_amount(self):
    331         target_id = self.sell_target_type_id
    332         if target_id is None:
    333             return
    334         else:
    335             buyer_fee, error = get_ship_skin_trading_svc().get_predicted_buyer_fee(skin_hex=self.skin_license.skin_hex, target_id=target_id)
    336             if error:
    337                 self.show_error_message(error)
    338                 return
    339             return buyer_fee.lp_amount
    340 
    341     def on_cancel_button(self, *args):
    342         self.SetModalResult(uiconst.ID_CANCEL)
    343 
    344     def OnSessionChanged(self, isRemote, session, change):
    345         if 'corprole' in change or 'corpid' in change or 'allianceid' in change:
    346             self.Close()
    347 
    348     @uthread2.debounce(wait=0.2)
    349     def show_error_message(self, error):
    350         message_player(GetByLabel(get_listing_error_text(error)))
    351 
    352     @property
    353     def has_sufficient_funds(self):
    354         if self.broker_fee is None:
    355             return False
    356         return sm.GetService('wallet').GetWealth() >= self.broker_fee
    357 
    358     @property
    359     def has_valid_target(self):
    360         if self.sell_availability == SellAvailability.SPECIFIC and self.sell_target_type_id is None:
    361             return False
    362         return True
    363 
    364     @property
    365     def tax_rate(self):
    366         return self._tax_rate
    367 
    368     @property
    369     def broker_fee(self):
    370         return self._broker_fee
    371 
    372     @property
    373     def buyer_fee_amount(self):
    374         return self._buyer_fee_amount
    375 
    376     @property
    377     def is_valid(self):
    378         return self.has_sufficient_funds and self.has_valid_target
    379 
    380     @property
    381     def currency(self):
    382         return self.currency_combo.GetValue()
    383 
    384     @property
    385     def price(self):
    386         return self.amount * self.copies
    387 
    388     @property
    389     def amount(self):
    390         return self.amount_edit.GetValue()
    391 
    392     @property
    393     def copies(self):
    394         return self.copies_edit.GetValue()
    395 
    396     @property
    397     def duration(self):
    398         return self.duration_combo.GetValue()
    399 
    400     @property
    401     def sell_availability(self):
    402         return self.availability_combo.GetValue()
    403 
    404     @property
    405     def sell_target(self):
    406         return self.search_field.completed_suggestion
    407 
    408     @property
    409     def sell_target_type_id(self):
    410         availability = self.sell_availability
    411         if availability == SellAvailability.MY_CORPORATION:
    412             return session.corpid
    413         if availability == SellAvailability.MY_ALLIANCE:
    414             return session.allianceid
    415         if availability == SellAvailability.SPECIFIC:
    416             sell_target = self.sell_target
    417             if sell_target:
    418                 return sell_target.owner_id
    419             return None