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

window.py (25885B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\dynamicresources\client\ess\bracket\debug\window.py
      2 import contextlib
      3 import datetime
      4 import uuid
      5 from collections import OrderedDict
      6 import datetimeutils
      7 import eveformat
      8 import evelink.client
      9 import eveui
     10 import gametime
     11 import localization
     12 import signals
     13 import uthread2
     14 from carbonui import uiconst
     15 from carbonui.control.combo import Combo
     16 from carbonui.primitives.container import Container
     17 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     18 from carbonui.primitives.fill import Fill
     19 from carbonui.primitives.flowcontainer import FlowContainer
     20 from carbonui.primitives.layoutGrid import LayoutGrid
     21 from eve.client.script.ui import eveColor
     22 from eve.client.script.ui.control import eveLabel
     23 from carbonui.control.button import Button
     24 from carbonui.control.checkbox import Checkbox
     25 from carbonui.control.window import Window
     26 from dynamicresources.client.ess.state import EssStateProvider, EssStatus, MainBankLink, MainBankLinkResult
     27 from dynamicresources.client.service import get_dynamic_resource_service
     28 
     29 class DebugEssStateProvider(EssStateProvider):
     30 
     31     def __init__(self, provider):
     32         if isinstance(provider, DebugEssStateProvider):
     33             raise RuntimeError('Double wrapping!')
     34         self._suppress_state_update = False
     35         super(DebugEssStateProvider, self).__init__(provider.state)
     36         self._real_provider = provider
     37         self._next_token_id = provider._next_token_id
     38         self._subscribers = provider._subscribers
     39         provider._subscribers = OrderedDict()
     40         self._need_fetch = provider._need_fetch
     41         self.on_fetch_request = provider.on_fetch_request
     42         provider.on_fetch_request = signals.Signal()
     43         self._overrides = {}
     44         self.history = [self._state]
     45         self.on_override_changed = signals.Signal()
     46 
     47     @EssStateProvider.state.setter
     48     def state(self, state):
     49         if self._real_provider is None:
     50             raise RuntimeError('Provider has been released')
     51         if state != self._real_provider.state:
     52             self.history.append(state)
     53         self._real_provider.state = state
     54         old_state = self._state
     55         self._state = self._override_state(state)
     56         self.notify_subscribers(old_state, self._state)
     57 
     58     def evolve(self, *args, **kwargs):
     59         self.state = self._real_provider.state.evolve(*args, **kwargs)
     60 
     61     def _override_state(self, state):
     62         if '__state__' in self._overrides:
     63             state = self._overrides['__state__']
     64         return state.evolve(status=self._overrides.get('status', state.status), in_range=self._overrides.get('in_range', state.in_range), main_bank=state.main_bank.evolve(balance=self._overrides.get('main_bank.balance', state.main_bank.balance), link=self._overrides.get('main_bank.link', state.main_bank.link), link_result=self._overrides.get('main_bank.link_result', state.main_bank.link_result)), reserve_bank=state.reserve_bank.evolve(balance=self._overrides.get('reserve_bank.balance', state.reserve_bank.balance), pulses_total=self._overrides.get('reserve_bank.pulses_total', state.reserve_bank.pulses_total), pulses_remaining=self._overrides.get('reserve_bank.pulses_remaining', state.reserve_bank.pulses_remaining), last_pulse_start=self._overrides.get('reserve_bank.last_pulse_start', state.reserve_bank.last_pulse_start), link_count=self._overrides.get('reserve_bank.link_count', state.reserve_bank.link_count), linked=self._overrides.get('reserve_bank.linked', state.reserve_bank.linked)))
     65 
     66     def release_provider(self):
     67         provider = self._real_provider
     68         self._real_provider = None
     69         provider._next_token_id = self._next_token_id
     70         provider._subscribers = self._subscribers
     71         self._subscribers = OrderedDict()
     72         provider._need_fetch = self._need_fetch
     73         provider.on_fetch_request = self.on_fetch_request
     74         self.on_fetch_request = signals.Signal()
     75         if self.state != provider.state:
     76             provider.notify_subscribers(self.state, provider.state)
     77         return provider
     78 
     79     @contextlib.contextmanager
     80     def atomic_override(self):
     81         old_suppress = self._suppress_state_update
     82         self._suppress_state_update = True
     83         old_overrides = self._overrides.copy()
     84         try:
     85             yield
     86         finally:
     87             self._suppress_state_update = old_suppress
     88 
     89         self._update_state(old_overrides)
     90 
     91     def set_override(self, path, value):
     92         old_overrides = self._overrides.copy()
     93         self._overrides[path] = value
     94         self._update_state(old_overrides)
     95 
     96     def has_override(self, path):
     97         return path in self._overrides
     98 
     99     def clear_override(self, path):
    100         old_overrides = self._overrides.copy()
    101         del self._overrides[path]
    102         self._update_state(old_overrides)
    103 
    104     def _update_state(self, old_overrides):
    105         if self._real_provider is None:
    106             raise RuntimeError('Provider has been released')
    107         if self._suppress_state_update:
    108             return
    109         old_state = self._state
    110         self._state = self._override_state(self._real_provider.state)
    111         self.notify_subscribers(old_state, self._state)
    112         if old_overrides != self._overrides:
    113             self.on_override_changed()
    114 
    115 
    116 class EssStateProviderOverride(object):
    117 
    118     def __init__(self, service):
    119         self._service = service
    120         self._wrapper = DebugEssStateProvider(service.ess_state_provider)
    121         service._ess_state_provider = self._wrapper
    122 
    123     def __del__(self):
    124         self._service._ess_state_provider = self._wrapper.release_provider()
    125 
    126 
    127 class EssDebugWindow(Window):
    128     default_name = 'EssDebugWindow'
    129     default_windowID = 'ess_debug_window'
    130     default_caption = 'ESS Debug Window'
    131     default_width = 300
    132     default_height = 400
    133     default_minSize = (default_width, default_height)
    134     default_left = '__center__'
    135     default_top = '__center__'
    136 
    137     def __init__(self, **kwargs):
    138         super(EssDebugWindow, self).__init__(**kwargs)
    139         self._state_update_token = None
    140         self._paused = False
    141         self._state_index = 0
    142         self._service = get_dynamic_resource_service()
    143         self._ess_state_provider_override = EssStateProviderOverride(self._service)
    144         self._is_reserve_bank_lock_overridden = False
    145         self._on_state_update()
    146         self.state_provider.on_override_changed.connect(self._redraw)
    147 
    148     @property
    149     def ess_state(self):
    150         return self.state_provider.state
    151 
    152     @property
    153     def state_provider(self):
    154         provider = self._service.ess_state_provider
    155         if not isinstance(provider, DebugEssStateProvider):
    156             raise RuntimeError('EssStateProvider not wrapped')
    157         return provider
    158 
    159     def Close(self, setClosed = False, *args, **kwargs):
    160         self._state_update_token = None
    161         if hasattr(self, '_ess_state_provider_override'):
    162             del self._ess_state_provider_override
    163         super(EssDebugWindow, self).Close(setClosed, *args, **kwargs)
    164 
    165     def _on_state_update(self, state = None):
    166         _, self._state_update_token = self.state_provider.watch(self._on_state_update)
    167         self._redraw()
    168 
    169     def _pause(self):
    170         self._paused = True
    171         self._state_index = len(self.state_provider.history) - 1
    172         self.state_provider.set_override('__state__', self.state_provider.history[self._state_index])
    173 
    174     def _resume(self):
    175         self._paused = False
    176         self.state_provider.clear_override('__state__')
    177 
    178     def _next_history(self):
    179         if self._state_index < len(self.state_provider.history) - 1:
    180             self._state_index += 1
    181             self.state_provider.set_override('__state__', self.state_provider.history[self._state_index])
    182 
    183     def _previous_history(self):
    184         if self._state_index > 0:
    185             self._state_index -= 1
    186             self.state_provider.set_override('__state__', self.state_provider.history[self._state_index])
    187 
    188     def _redraw(self):
    189         if self.destroyed:
    190             return
    191         self.GetMainArea().Flush()
    192         main = Container(parent=self.GetMainArea(), align=uiconst.TOALL, padding=12)
    193         change_color = eveColor.CRYO_BLUE
    194         history_cont = Container(parent=Container(parent=self.GetMainArea(), align=uiconst.TOBOTTOM, height=52, padding=(2, 0, 2, 2), bgColor=(0.0, 0.0, 0.0, 0.3)), align=uiconst.TOALL, padding=(16, 8, 16, 8))
    195         if not self._paused:
    196             Button(parent=history_cont, align=uiconst.CENTERLEFT, minHeight=32, sidePadding=16, label='PAUSE', func=self._pause, args=())
    197             eveLabel.EveLabelMedium(parent=history_cont, align=uiconst.CENTERRIGHT, text='{} updates'.format(len(self.state_provider.history)))
    198         else:
    199             Fill(parent=history_cont.parent, align=uiconst.TOLEFT_NOPUSH, width=3, color=change_color)
    200             Button(parent=history_cont, align=uiconst.CENTERLEFT, minHeight=32, sidePadding=16, label='RESUME', func=self._resume, args=())
    201             grid = LayoutGrid(parent=history_cont, align=uiconst.CENTERRIGHT, columns=3, cellSpacing=8)
    202             prev_button = Button(parent=grid, align=uiconst.CENTER, label='PREVIOUS', func=self._previous_history, args=())
    203             if self._state_index == 0:
    204                 prev_button.Disable()
    205             eveLabel.EveLabelMedium(parent=grid, align=uiconst.CENTER, text='{} / {}'.format(self._state_index + 1, len(self.state_provider.history)))
    206             next_button = Button(parent=grid, align=uiconst.CENTER, label='NEXT', func=self._next_history, args=())
    207             if self._state_index >= len(self.state_provider.history) - 1:
    208                 next_button.Disable()
    209         left_pad = 21
    210         value_color = eveColor.BLACK
    211         state_cont = ContainerAutoSize(parent=main, align=uiconst.TOTOP)
    212 
    213         def field(parent, checked, callback):
    214             wrap = ContainerAutoSize(parent=parent, align=uiconst.TOTOP, alignMode=uiconst.TOTOP, padBottom=8)
    215             Fill(parent=wrap, align=uiconst.TOLEFT_NOPUSH, left=-10, width=3, color=change_color if checked else (0.2, 0.2, 0.2))
    216             Checkbox(parent=ContainerAutoSize(parent=wrap, align=uiconst.TOLEFT, minHeight=16), align=uiconst.CENTERTOP, top=1, checked=checked, callback=callback, hint='clear override' if checked else 'override')
    217             return wrap
    218 
    219         is_status_overridden = self.state_provider.has_override('status')
    220         status_field = field(parent=state_cont, checked=is_status_overridden, callback=self._on_status_override)
    221         if is_status_overridden:
    222             status_cont = LayoutGrid(parent=status_field, align=uiconst.TOTOP, columns=2)
    223             eveLabel.EveLabelMedium(parent=status_cont, align=uiconst.CENTER, text='The ESS is')
    224             Combo(parent=status_cont, align=uiconst.CENTER, left=4, options=map(lambda s: (s.name, s), iter(EssStatus)), callback=self._on_status, select=self.ess_state.status)
    225         else:
    226             eveLabel.EveLabelMedium(parent=status_field, align=uiconst.TOTOP, text='The ESS is {}'.format(eveformat.bold(eveformat.color(self.ess_state.status.name, value_color))))
    227         is_range_overridden = self.state_provider.has_override('in_range')
    228         range_field = field(parent=state_cont, checked=is_range_overridden, callback=self._on_range_override)
    229         if not is_range_overridden:
    230             eveLabel.EveLabelMedium(parent=range_field, align=uiconst.TOTOP, text='You are {}'.format(eveformat.bold(eveformat.color('in range' if self.ess_state.in_range else 'out of range', value_color))))
    231         else:
    232             range_cont = LayoutGrid(parent=range_field, align=uiconst.TOTOP, columns=2)
    233             eveLabel.EveLabelMedium(parent=range_cont, align=uiconst.CENTER, text='You are')
    234             Combo(parent=range_cont, align=uiconst.CENTER, left=4, options=[('in range', True), ('out of range', False)], callback=self._on_range, select=self.ess_state.in_range)
    235         eveLabel.EveCaptionSmall(parent=state_cont, align=uiconst.TOTOP, text='MAIN BANK', padding=(left_pad,
    236          16,
    237          0,
    238          0))
    239         eveLabel.EveLabelMedium(parent=state_cont, align=uiconst.TOTOP, text='The balance is {}'.format(eveformat.color(eveformat.isk(self.ess_state.main_bank.balance), value_color)), padding=(left_pad,
    240          4,
    241          0,
    242          16))
    243         is_link_overridden = self.state_provider.has_override('main_bank.link')
    244         link_field = field(parent=state_cont, checked=is_link_overridden, callback=self._on_link_override)
    245         if not self.ess_state.main_bank.link:
    246             eveLabel.EveLabelMedium(parent=link_field, align=uiconst.TOTOP, text='No one is currently hacking')
    247         else:
    248             eveLabel.EveLabelMedium(parent=ContainerAutoSize(parent=link_field, align=uiconst.TOTOP), align=uiconst.TOPLEFT, state=uiconst.UI_NORMAL, text='{} is hacking'.format(eveformat.color(evelink.character_link(self.ess_state.main_bank.link.character_id), value_color)))
    249             timer_label = eveLabel.EveLabelMedium(parent=ContainerAutoSize(parent=link_field, align=uiconst.TOTOP, padding=(0, 4, 0, 4)), align=uiconst.TOTOP)
    250 
    251             def update_time(label, state):
    252                 link = state.main_bank.link
    253                 while not label.destroyed:
    254                     remaining = link.end - gametime.now_sim()
    255                     if remaining.total_seconds() > 0:
    256                         text = '{} remaining'.format(eveformat.color(format_delta_time(remaining), value_color))
    257                     else:
    258                         text = 'Completion pending ...'
    259                     label.SetText(text)
    260                     eveui.wait_for_next_frame()
    261 
    262             uthread2.start_tasklet(update_time, timer_label, self.ess_state)
    263             if is_link_overridden:
    264                 button_cont = FlowContainer(parent=ContainerAutoSize(parent=link_field, align=uiconst.TOTOP, padTop=4), align=uiconst.TOTOP, contentSpacing=(4, 4))
    265                 Button(parent=button_cont, align=uiconst.NOALIGN, label='UNLINK', minHeight=32, sidePadding=16, func=self._fail_hack, args=())
    266                 Button(parent=button_cont, align=uiconst.NOALIGN, label='COMPLETE', minHeight=32, sidePadding=16, func=self._succeed_hack, args=())
    267                 Button(parent=button_cont, align=uiconst.NOALIGN, label='+1m', minHeight=32, sidePadding=16, func=self._advance_hack, args=(datetime.timedelta(minutes=1),))
    268                 Button(parent=button_cont, align=uiconst.NOALIGN, label='-1m', minHeight=32, sidePadding=16, func=self._advance_hack, args=(datetime.timedelta(minutes=-1),))
    269         if not self.ess_state.main_bank.link and self.ess_state.main_bank.link_result:
    270             link_result = self.ess_state.main_bank.link_result
    271             character = evelink.character_link(link_result.character_id)
    272             if link_result.successful:
    273                 text = '{} completed a hack'.format(character)
    274             else:
    275                 text = '{} disconnected'.format(character)
    276             eveLabel.EveLabelMedium(parent=link_field, align=uiconst.TOTOP, state=uiconst.UI_NORMAL, padding=(0, 4, 0, 4), text=text)
    277         if is_link_overridden and not self.ess_state.main_bank.link:
    278             button_cont = FlowContainer(parent=ContainerAutoSize(parent=link_field, align=uiconst.TOTOP, padTop=4), align=uiconst.TOTOP, contentSpacing=(4, 4))
    279             Button(parent=button_cont, align=uiconst.NOALIGN, label='LINK', func=self._fake_hack, args=())
    280             Button(parent=button_cont, align=uiconst.NOALIGN, label='LINK (10s)', func=self._fake_hack, args=datetime.timedelta(seconds=10))
    281         eveLabel.EveCaptionSmall(parent=state_cont, align=uiconst.TOTOP, text='Reserve bank', padding=(left_pad,
    282          16,
    283          0,
    284          0))
    285         eveLabel.EveLabelMedium(parent=state_cont, align=uiconst.TOTOP, text='The balance is {}'.format(eveformat.color(eveformat.isk(self.ess_state.reserve_bank.balance), value_color)), padding=(left_pad,
    286          4,
    287          0,
    288          4))
    289         lock_field = field(parent=state_cont, checked=self._is_reserve_bank_lock_overridden, callback=self._on_reserve_bank_lock_override)
    290         if self.ess_state.reserve_bank.locked:
    291             eveLabel.EveLabelMedium(parent=lock_field, align=uiconst.TOTOP, text='The bank is {}'.format(eveformat.color('LOCKED', value_color)))
    292             if self._is_reserve_bank_lock_overridden:
    293                 button_cont = FlowContainer(parent=ContainerAutoSize(parent=lock_field, align=uiconst.TOTOP, padTop=4), align=uiconst.TOTOP, contentSpacing=(4, 4))
    294                 Button(parent=button_cont, align=uiconst.NOALIGN, label='UNLOCK (15m)', func=self._unlock_reserve_bank, args=(15,))
    295                 Button(parent=button_cont, align=uiconst.NOALIGN, label='UNLOCK (45m)', func=self._unlock_reserve_bank, args=(45,))
    296         else:
    297             eveLabel.EveLabelMedium(parent=lock_field, align=uiconst.TOTOP, text='{} of {} pulses remaining'.format(eveformat.color(self.ess_state.reserve_bank.pulses_remaining, value_color), eveformat.color(self.ess_state.reserve_bank.pulses_total, value_color)))
    298             last_pulse_start_label = eveLabel.EveLabelMedium(parent=lock_field, align=uiconst.TOTOP)
    299 
    300             def update_last_pulse_start_label():
    301                 while not last_pulse_start_label.destroyed:
    302                     last_pulse_start_label.text = 'Pulse started {} ago'.format(eveformat.color(format_delta_time(gametime.now_sim() - self.ess_state.reserve_bank.last_pulse_start), value_color))
    303                     eveui.wait_for_next_frame()
    304 
    305             uthread2.start_tasklet(update_last_pulse_start_label)
    306             if self._is_reserve_bank_lock_overridden:
    307                 grid = LayoutGrid(parent=lock_field, align=uiconst.TOTOP, top=4, columns=4, cellSpacing=4)
    308                 decrease_button = Button(parent=grid, fixedwidth=18, label='-', func=self._remove_reserve_bank_link, args=())
    309                 link_count = self.ess_state.reserve_bank.link_count
    310                 linked = self.ess_state.reserve_bank.linked
    311                 if link_count == 0 or link_count == 1 and linked:
    312                     decrease_button.Disable()
    313                 eveLabel.EveLabelMedium(parent=grid, text=eveformat.color(self.ess_state.reserve_bank.link_count, value_color))
    314                 Button(parent=grid, fixedwidth=18, label='+', func=self._add_reserve_bank_link, args=())
    315                 eveLabel.EveLabelMedium(parent=grid, text='active links')
    316             else:
    317                 eveLabel.EveLabelMedium(parent=lock_field, align=uiconst.TOTOP, text='{} active links'.format(eveformat.color(self.ess_state.reserve_bank.link_count, value_color)))
    318             eveLabel.EveLabelMedium(parent=lock_field, align=uiconst.TOTOP, top=4, text='You are {}'.format(eveformat.color('linked' if self.ess_state.reserve_bank.linked else 'not linked', value_color)))
    319             if self._is_reserve_bank_lock_overridden:
    320                 button_cont = FlowContainer(parent=ContainerAutoSize(parent=lock_field, align=uiconst.TOTOP, padTop=8), align=uiconst.TOTOP, contentSpacing=(4, 4))
    321                 if self.ess_state.reserve_bank.linked:
    322                     Button(parent=button_cont, align=uiconst.NOALIGN, label='UNLINK', func=self._unlink_from_reserve_bank, args=())
    323                 else:
    324                     Button(parent=button_cont, align=uiconst.NOALIGN, label='LINK', func=self._link_to_reserve_bank, args=())
    325                 if self.ess_state.reserve_bank.pulses_remaining > 0:
    326                     Button(parent=button_cont, align=uiconst.NOALIGN, label='PULSE', func=self._pulse_reserve_bank, args=())
    327                     Button(parent=button_cont, align=uiconst.NOALIGN, label='FINISH', func=self._lock_reserve_bank, args=())
    328 
    329     def _on_status_override(self, checkbox):
    330         override = bool(checkbox.GetValue())
    331         if override:
    332             self.state_provider.set_override('status', self.ess_state.status)
    333         else:
    334             self.state_provider.clear_override('status')
    335 
    336     def _on_status(self, combo, key, value):
    337         self.state_provider.set_override('status', value)
    338 
    339     def _on_range_override(self, checkbox):
    340         override = bool(checkbox.GetValue())
    341         if override:
    342             self.state_provider.set_override('in_range', self.ess_state.in_range)
    343         else:
    344             self.state_provider.clear_override('in_range')
    345 
    346     def _on_range(self, combo, key, value):
    347         self.state_provider.set_override('in_range', value)
    348 
    349     def _on_link_override(self, checkbox):
    350         override = bool(checkbox.GetValue())
    351         if override:
    352             with self.state_provider.atomic_override():
    353                 self.state_provider.set_override('main_bank.link', self.ess_state.main_bank.link)
    354                 self.state_provider.set_override('main_bank.link_result', self.ess_state.main_bank.link_result)
    355         else:
    356             with self.state_provider.atomic_override():
    357                 self.state_provider.clear_override('main_bank.link')
    358                 self.state_provider.clear_override('main_bank.link_result')
    359 
    360     def _fake_hack(self, duration = None):
    361         if duration is None:
    362             duration = datetime.timedelta(minutes=6)
    363         start = gametime.now_sim()
    364         end = start + duration
    365         self.state_provider.set_override('main_bank.link', MainBankLink(uuid.uuid4(), session.charid, start, end))
    366 
    367     def _fail_hack(self):
    368         self._finish_hack(successful=False)
    369 
    370     def _succeed_hack(self):
    371         self._finish_hack(successful=True)
    372 
    373     def _finish_hack(self, successful):
    374         if self.ess_state.main_bank.link is None:
    375             return
    376         link = self.ess_state.main_bank.link
    377         with self.state_provider.atomic_override():
    378             self.state_provider.set_override('main_bank.link', None)
    379             self.state_provider.set_override('main_bank.link_result', MainBankLinkResult(link_id=self.ess_state.main_bank.link.link_id, character_id=link.character_id, successful=successful))
    380 
    381     def _advance_hack(self, delta):
    382         if self.ess_state.main_bank.link is None:
    383             return
    384         link = self.ess_state.main_bank.link
    385         self.state_provider.set_override('main_bank.link', link.evolve(start=link.start - delta, end=link.end - delta))
    386 
    387     def _on_reserve_bank_lock_override(self, checkbox):
    388         override = bool(checkbox.GetValue())
    389         if override:
    390             self._is_reserve_bank_lock_overridden = True
    391             with self.state_provider.atomic_override():
    392                 self.state_provider.set_override('reserve_bank.pulses_total', self.state_provider.state.reserve_bank.pulses_total)
    393                 self.state_provider.set_override('reserve_bank.pulses_remaining', self.state_provider.state.reserve_bank.pulses_remaining)
    394                 self.state_provider.set_override('reserve_bank.link_count', self.state_provider.state.reserve_bank.link_count)
    395                 self.state_provider.set_override('reserve_bank.last_pulse_start', self.state_provider.state.reserve_bank.last_pulse_start)
    396                 self.state_provider.set_override('reserve_bank.linked', False)
    397         else:
    398             self._is_reserve_bank_lock_overridden = False
    399             with self.state_provider.atomic_override():
    400                 self.state_provider.clear_override('reserve_bank.pulses_total')
    401                 self.state_provider.clear_override('reserve_bank.pulses_remaining')
    402                 self.state_provider.clear_override('reserve_bank.link_count')
    403                 self.state_provider.clear_override('reserve_bank.last_pulse_start')
    404                 self.state_provider.clear_override('reserve_bank.linked')
    405 
    406     def _unlock_reserve_bank(self, pulses):
    407         with self.state_provider.atomic_override():
    408             self.state_provider.set_override('reserve_bank.pulses_total', pulses)
    409             self.state_provider.set_override('reserve_bank.pulses_remaining', pulses)
    410             self.state_provider.set_override('reserve_bank.link_count', 0)
    411             self.state_provider.set_override('reserve_bank.last_pulse_start', gametime.now_sim())
    412 
    413     def _lock_reserve_bank(self):
    414         with self.state_provider.atomic_override():
    415             self.state_provider.set_override('reserve_bank.pulses_total', 0)
    416             self.state_provider.set_override('reserve_bank.pulses_remaining', 0)
    417             self.state_provider.set_override('reserve_bank.link_count', 0)
    418             self.state_provider.set_override('reserve_bank.last_pulse_start', None)
    419             self.state_provider.set_override('reserve_bank.linked', False)
    420 
    421     def _pulse_reserve_bank(self):
    422         with self.state_provider.atomic_override():
    423             self.state_provider.set_override('reserve_bank.pulses_remaining', self.state_provider.state.reserve_bank.pulses_remaining - 1)
    424             self.state_provider.set_override('reserve_bank.last_pulse_start', gametime.now_sim())
    425 
    426     def _add_reserve_bank_link(self):
    427         self.state_provider.set_override('reserve_bank.link_count', self.state_provider.state.reserve_bank.link_count + 1)
    428 
    429     def _remove_reserve_bank_link(self):
    430         self.state_provider.set_override('reserve_bank.link_count', self.state_provider.state.reserve_bank.link_count - 1)
    431 
    432     def _link_to_reserve_bank(self):
    433         with self.state_provider.atomic_override():
    434             self._add_reserve_bank_link()
    435             self.state_provider.set_override('reserve_bank.linked', True)
    436 
    437     def _unlink_from_reserve_bank(self):
    438         with self.state_provider.atomic_override():
    439             self._remove_reserve_bank_link()
    440             self.state_provider.set_override('reserve_bank.linked', False)
    441 
    442 
    443 def format_delta_time(delta_time):
    444     return localization.formatters.FormatTimeIntervalShortWritten(datetimeutils.timedelta_to_filetime_delta(delta_time), showFrom='hour', showTo='second')
    445 
    446 
    447 def __reload_update__(old_namespace):
    448     window = EssDebugWindow.GetIfOpen()
    449     if window:
    450         window.Close()
    451         EssDebugWindow.Open()