Esempio n. 1
0
File: util.py Progetto: ursi/sbpe
def firstChild(obj):
    if obj == ffi.NULL:
        return ffi.NULL
    cv = ffi.cast('struct UIElementContainer *', obj).children
    if cv.finish == cv.start or cv.endOfStorage <= cv.start:
        return ffi.NULL
    return ffi.cast('struct UIElement **', cv.start)[0]
Esempio n. 2
0
def kickstart():
    global refs, util
    import os

    SYMFILE = os.environ['SBPE_SYMFILE']

    with open(SYMFILE, 'rb') as f:
        offsets = json.loads(f.read().decode('utf-8'))

    refs.SCRIPTPATH = os.path.dirname(SYMFILE)
    sys.path.insert(1, refs.SCRIPTPATH)
    sys.path.insert(1, os.path.join(refs.SCRIPTPATH, 'pypy', 'site-packages'))

    logging.basicConfig(
        level=logging.INFO,
        filename=os.path.join(refs.SCRIPTPATH, LOGFILE),
        filemode='w',
        format='%(asctime)s %(module)s [%(levelname)s] %(message)s')

    logging.info('SBPE ' + refs.VERSION)
    logging.info('platform: ' + refs.SYSINFO)

    refs.CONFIGFILE = os.path.join(refs.SCRIPTPATH, CONFIGFILE)
    refs.config.read(refs.CONFIGFILE)

    # import native functions/objects
    for sname in offsets:
        offset = offsets[sname]
        if sname in SYMTYPES:
            # objects, variables
            refs[sname] = ffi.cast(SYMTYPES[sname], offset)
        elif sname.startswith('flags::'):
            # flags
            refs[sname[7:]] = ffi.cast('uint8_t *', offset)
        else:
            # functions
            refs[sname] = ffi.cast('p' + sname, offset)

    # import object inheritance info
    with open(os.path.join(refs.SCRIPTPATH, 'cdefs/proto.json'), 'r') as f:
        d = json.load(f)
        refs.CHAINS = d['chains']
        refs.CASTABLE = d['castable']

    # import util
    util = importlib.import_module('util')
    util.refs = refs

    # import plugins
    plugpath = os.path.join(refs.SCRIPTPATH, 'plugins')
    man = importlib.import_module('manager')
    refs.manager = man.Manager(path=plugpath, refs=refs)

    # init hooks
    initHooks()

    logging.info('startup ok')
    return 0
Esempio n. 3
0
File: util.py Progetto: ursi/sbpe
def getUITree(obj, depth=0):
    '''printable UI element tree starting from obj'''
    if obj == ffi.NULL:
        return ' ' * depth + 'NULL'
    cname = getClassName(obj)
    uiel = ffi.cast('struct UIElement*', obj)
    res = ' ' * depth + '{0} ({1.x}, {1.y}) {1.w}x{1.h}'.format(cname, uiel)
    if cname in refs.CASTABLE['UIElementContainer']:
        uiec = ffi.cast('struct UIElementContainer*', obj)
        for elem in vec2list(uiec.children):
            res += '\n' + getUITree(elem, depth + 2)
    return res
Esempio n. 4
0
File: util.py Progetto: ursi/sbpe
def getClassName(obj):
    '''
    class name of a C++ object (assuming gcc memory layout).
    doesn't demangle complicated names
    '''
    if obj == ffi.NULL:
        return 'NULL'

    # class pointer is always at [0]
    classptr = ffi.cast('void****', obj)[0]
    # vtable (1 up) -> type -> name (1 down)
    nameptr = classptr[-1][1]
    cname = ffi.string(ffi.cast('char*', nameptr), 100)
    return cname[1 if len(cname) < 11 else 2:].decode()
Esempio n. 5
0
File: util.py Progetto: ursi/sbpe
def vec2list(vector, itemtype='void*'):
    '''std::vector -> list'''
    if vector.start == ffi.NULL or vector.finish == ffi.NULL or\
            vector.endOfStorage <= vector.start:
        return []
    n = (vector.finish - vector.start) // ffi.sizeof(itemtype)
    return ffi.unpack(ffi.cast(itemtype + '*', vector.start), n)
Esempio n. 6
0
File: hud.py Progetto: ursi/sbpe
    def afterUpdate(self):
        self.draw = False
        wc = self.refs.WorldClient
        cw = self.refs.ClientWorld
        if wc == ffi.NULL or cw == ffi.NULL or cw.player == ffi.NULL:
            return

        if wc.hud != ffi.NULL and wc.hud.hudStatus != ffi.NULL:
            ffi.cast('struct UIElement *', wc.hud.hudStatus).show = False

        ptype = util.getClassName(cw.player)
        if ptype not in self.refs.CASTABLE['PlayerCharacter']:
            return

        wobj = ffi.cast('struct WorldObject *', cw.player)
        pc = ffi.cast('struct PlayerCharacter *', cw.player)

        self.txt_hp.text = '{}'.format(wobj.props.hitpoints)
        self.txt_hpmax.text = '/{}'.format(wobj.props.maxhitpoints)

        self.txt_ammo.text = '{}'.format(pc.ammo)
        maxammo = pc.maxAmmo.base + pc.maxAmmo.bonus
        if pc.ammoMult > 1:
            self.txt_ammomax.text = '/{} (x{})'.format(maxammo, pc.ammoMult)
        else:
            self.txt_ammomax.text = '/{}'.format(maxammo)

        self.txt_currency.text = '{} EC  {} UC'.format(pc.ec, pc.uc)

        for name in ELEMS:
            el = getattr(self, 'txt_' + name)
            el.size = getattr(self.config, 'size_' + name)
            el.outlineSize = self.config.outline

        if wobj.props.hitpoints == wobj.props.maxhitpoints:
            hpcolor = self.config.color_hp_full
        else:
            hpcolor = self.config.color_hp
        self.txt_hp.color = self.txt_hpmax.color = hpcolor
        self.txt_ammo.color = self.txt_ammomax.color = self.config.color_ammo
        self.txt_currency.color = self.config.color_currency

        self.draw = True
Esempio n. 7
0
def hook_LoadTextureFile(name, callback, userData):
    refs._tfname = ffi.string(name).decode()

    hook = lib.subhook_new(callback, lib.hook_textureCallback, 0)
    refs._orig_callback = ffi.cast('XDL_LoadTextureDoneCallback',
                                   lib.subhook_get_trampoline(hook))

    lib.subhook_install(hook)
    ORIGS['XDL_LoadTextureFile'](name, callback, userData)
    lib.subhook_remove(hook)
    lib.subhook_free(hook)
Esempio n. 8
0
File: util.py Progetto: ursi/sbpe
def updateState():
    '''make commonly used data more accessible to plugins'''
    if GLFUNCTIONS[0] not in refs:
        loadGLFunctions()

    if refs.stage[0] == ffi.NULL:
        return

    # top level children of stage
    tops = []
    types = []
    for t in vec2list(refs.stage[0].asUIElementContainer.children):
        clname = getClassName(t)
        types.append(clname)
        if clname in STRUCTTYPES:
            t = ffi.cast('struct {} *'.format(clname), t)
        tops.append(t)
    refs.tops = tops
    refs.topTypes = types

    # main menu
    if types[0] == 'MainMenu':
        refs.MainMenu = tops[0]
    else:
        refs.MainMenu = ffi.NULL

    # game client
    refs.GameClient = refs.WorldClient = refs.ClientWorld =\
        refs.WorldView = ffi.NULL

    if types[0] == 'GameClient':
        refs.GameClient = tops[0]
        refs.WorldClient = refs.GameClient.worldClient
        if refs.WorldClient != ffi.NULL:
            refs.WorldView = refs.WorldClient.worldView
            refs.ClientWorld = refs.WorldClient.clientWorld
            if refs.ClientWorld == ffi.NULL or refs.WorldView == ffi.NULL:
                refs.ClientWorld = refs.WorldView = ffi.NULL

    # window size and scale
    ww_ = ffi.new('int *')
    wh_ = ffi.new('int *')
    lib.SDL_GetWindowSize(refs.window_[0], ww_, wh_)
    ww = ww_[0] or 1  # avoid potential division by zero
    wh = wh_[0] or 1
    refs.windowW = ww
    refs.windowH = wh
    refs.scaleX = refs.canvasW_[0] / ww
    refs.scaleY = refs.canvasH_[0] / wh
Esempio n. 9
0
    def addhook(fname, hookfunc, ret=False):
        hook = lib.subhook_new(refs[fname], hookfunc, 1)
        orig = ffi.cast('p' + fname, lib.subhook_get_trampoline(hook))
        if orig != ffi.NULL:
            ORIGS[fname] = orig
        else:
            logging.info('{}: no trampoline, using fallback'.format(fname))

            def call_orig(*args):
                lib.subhook_remove(hook)
                res = refs[fname](*args)
                lib.subhook_install(hook)
                if ret:
                    return res

            ORIGS[fname] = call_orig

        lib.subhook_install(hook)
        if not lib.subhook_is_installed(hook):
            logging.error('failed to hook {}'.format(fname))
Esempio n. 10
0
File: noise.py Progetto: ursi/sbpe
    def afterUpdate(self):
        gc = self.refs.GameClient
        if gc != ffi.NULL and gc.chatWindow != ffi.NULL:
            if self.config.hide_chat:
                ffi.cast('struct UIElement *', gc.chatWindow).x = -9000
            else:
                ffi.cast('struct UIElement *', gc.chatWindow).x = 0

        cw = self.refs.ClientWorld
        wv = self.refs.WorldView
        if cw == ffi.NULL or wv == ffi.NULL:
            return

        # other players
        allies = util.vec2list(cw.allies, 'struct WorldObject *')
        for obj in allies:
            props = obj.props
            if self.config.hide_shells:
                # fail: triggers game's "unknown object" box drawing
                ffi.cast('int *', props.vid.s)[-3] = 0

            if self.config.hide_names:
                # remember and remove relevant bits; also hides factions
                bits = props._has_bits[0] & 0x300000
                props._has_bits[0] ^= bits
                self.oldbits.append((obj, bits))
            elif self.config.hide_factions:
                # hide only faction names by zeroing faction name length
                if props.playerdata != ffi.NULL:
                    fname = props.playerdata.factionname
                    ffi.cast('int *', fname.s)[-3] = 0

            if self.config.hide_healthbars:
                # not sure if this breaks anything
                props.hitpoints = props.maxhitpoints

        if self.config.hide_ally_objects:
            worlds = util.sVecMap2list(cw.clientSubWorlds,
                                       'struct ForeignSubWorld *')
            for csubworld in worlds:
                for obj in util.worldobjects(csubworld):
                    if obj in allies:
                        # shells are treated separately
                        continue
                    if obj.props.terraintype > 0:
                        # anything that acts like terrain (macrons)
                        continue
                    if util.getstr(obj.props.vid)[:5] == 'timer':
                        # macron timer
                        continue
                    self.hide(obj)

        # reduce damage flash time
        rt = round((1 - self.config.damage_flash_intensity) * 132)
        rt = max(0, min(132, rt))
        if rt > 0:
            # server world time
            swt = ffi.cast('struct SubWorld *', cw.serverSubWorld).t
            # skip the beginning since lastDamageT is 0 by default
            if swt > 132:
                for obj in util.worldobjects(cw.serverSubWorld):
                    dt = swt - obj.lastDamageT
                    if dt < rt:
                        obj.lastDamageT = swt - rt

        for graphic in util.vec2list(wv.dynamicGraphics):
            gtype = util.getClassName(graphic)
            if self.config.hide_effects and gtype == 'AnimationGraphic':
                g = ffi.cast('struct AnimationGraphic *', graphic)
                g.startTime = 0

        if self.config.replace_shake:
            ct = time.perf_counter()
            if wv.shakePos < wv.shakeDuration and wv.shakeMagnitude > 0:
                self._shake = max(self._shake, ct + wv.shakeDuration / 1000)
            wv.shakeMagnitude = 0

            flashduration = wv.flashStart + wv.flashHold + wv.flashEnd
            if wv.flashPos < flashduration and wv.flashColor != 0:
                self._flash = max(self._flash, ct + flashduration / 1000)
            wv.flashColor = 0
Esempio n. 11
0
File: zoom.py Progetto: ursi/sbpe
    def afterUpdate(self):
        cw = self.refs.canvasW_
        ch = self.refs.canvasH_

        cscale = cw[0] / self.refs.windowW
        ctime = time.perf_counter()

        if not self.config.active:
            self.start = self.target = 1
            nscale = 1
        else:
            twidth = self.config.level
            targ = twidth / self.refs.windowW

            if targ <= 0:
                targ = 1

            if self.target != targ:
                self.stime = ctime
                self.start = cscale
                self.target = targ

            if self.config.time > 0:
                nscale = ease(self.start, self.target, self.stime, ctime,
                              self.config.time)
            else:
                nscale = self.target

        if nscale <= 0:
            nscale = 0.1

        if nscale == cscale:
            return

        tw = round(nscale * self.refs.windowW)
        th = round(nscale * self.refs.windowH)

        cw[0] = self.refs.overrideW = tw
        ch[0] = self.refs.overrideH = th

        installed = lib.subhook_is_installed(self._hook) != 0
        if self.config.fast and not installed:
            lib.subhook_install(self._hook)
        if not self.config.fast and installed:
            lib.subhook_remove(self._hook)

        # replacement handling of window resize event for WorldClient and
        # everything inside of it that doesn't have handlers
        wc = self.refs.WorldClient
        if self.config.fast and wc != ffi.NULL:
            setUIElementSize(wc, tw, th)
            setUIElementSize(wc.worldView, tw, th)
            setUIElementSize(wc.hud, tw, th)
            setUIElementSize(wc.overlay, tw, th)
            # equip
            if wc.hud != ffi.NULL and wc.hud.hudEquip != ffi.NULL:
                equip = toUIElement(wc.hud.hudEquip)
                equip.x = tw - equip.w - 28
            # overlays
            overlay = wc.overlay
            if overlay != ffi.NULL:
                otype = util.getClassName(overlay)
                overlay = ffi.cast('struct {} *'.format(otype), overlay)

                if otype == 'InventoryOverlay':
                    inv = toUIElement(overlay.inventoryWindow)
                    inv.x = tw - inv.w - 24
                    exy = inv.y + inv.h + 24
                    stash = toUIElement(overlay.stashWindow)
                    if stash != ffi.NULL:
                        stash.x = tw - stash.w - 24
                        stash.y = exy + 6
                        exy += stash.h + 6
                    ex = toUIElement(overlay.playerWindowExitSprite)
                    ex.x = tw - ex.w
                    ex.y = exy
                    tt = toUIElement(overlay.toolTip)
                    if tt != ffi.NULL:
                        tt.x = inv.x - tt.w
                    ctt = toUIElement(overlay.comparisonToolTip)
                    if ctt != ffi.NULL:
                        ctt.x = tt.x - ctt.w

                if otype == 'ProgressOverlay':
                    pw = toUIElement(overlay.progressWindow)
                    pw.x = (tw - pw.w) // 2
                    pw.y = (th - pw.h) // 2
                    ex = toUIElement(overlay.playerWindowExitSprite)
                    ex.x = tw - ex.w
                    ex.y = pw.y + pw.h - ex.h

                if otype == 'ScoreOverlay':
                    off = int(tw * 0.4)
                    scw = toUIElement(overlay.scoreCharWindow)
                    scw.x = (off - scw.w) // 2
                    ssw = toUIElement(overlay.scoreStatsWindow)
                    ssw.x = off
                    sbw = toUIElement(overlay.scoreBonusWindow)
                    sbw.x = off + ssw.w + 30
                    fss = toUIElement(overlay.finalScoreSprite)
                    fss.x = off
                    fss.y = max(sbw.h + sbw.y, ssw.h + ssw.y) + 64
                    xpb = toUIElement(overlay.xpLevelBars)
                    if xpb != ffi.NULL:
                        xpb.x = off
                        xpb.y = fss.h + fss.y + 24
                    ex = toUIElement(overlay.playerWindowExitSprite)
                    ex.x = tw - ex.w
                    ex.y = th - ex.h - 100
                    if overlay.scoreRankAdded:
                        # the rank visual element is the last child
                        oc = ffi.cast('struct UIElementContainer *', overlay)
                        chl = util.vec2list(oc.children, 'struct UIElement *')
                        if len(chl) > 0:
                            sr = chl[-1]
                            sr.x = scw.x + scw.w - 20

                if otype == 'ZoneScoreOverlay':
                    outro = toUIElement(overlay.outro)
                    outro.x = int(tw * 0.3)
                    outro.y = int(th * 0.5)
                    hoff = int(tw * 0.3)
                    voff = int(th * 0.7)
                    kvl = util.vec2list(overlay.keyValLabels,
                                        'struct LabelPair')
                    for pair in kvl:
                        first = toUIElement(pair.first)
                        second = toUIElement(pair.second)
                        first.x = hoff - 96
                        second.x = hoff + 96 - second.w
                        first.y = second.y = voff
                        voff += 20
                    if len(kvl) > 0:
                        voff += 20
                    xpb = toUIElement(overlay.xpLevelBars)
                    xpb.x = hoff - 140
                    xpb.y = voff + 20
                    ex = toUIElement(overlay.playerWindowExitSprite)
                    ex.x = tw - ex.w
                    ex.y = th - ex.h - 100

        # re-center camera
        if self.refs.WorldView != ffi.NULL:
            self.refs.WorldView.offsetsInitialized = False

        self.refs.windowEventCallback(lib.XDL_WINDOWEVENT_SIZE_CHANGED,
                                      self.refs.userData_[0])

        self.refs.overrideW = 0
        self.refs.overrideH = 0

        # hack to avoid 1 frame of wrong text scaling
        self.refs.scaleX = self.refs.scaleY = nscale
Esempio n. 12
0
File: zoom.py Progetto: ursi/sbpe
def toUIElement(obj):
    return ffi.cast('struct UIElement *', obj)
Esempio n. 13
0
File: util.py Progetto: ursi/sbpe
def sVecMap2list(svecmap, itemtype='void*'):
    '''struct SortedVecMap -> list'''
    lst = vec2list(svecmap.vec, 'struct SortedVecElement')
    for i in range(len(lst)):
        lst[i] = ffi.cast(itemtype, lst[i].obj)
    return lst
Esempio n. 14
0
File: util.py Progetto: ursi/sbpe
def loadGLFunctions():
    for name in GLFUNCTIONS:
        refs[name] = ffi.cast('p' + name,
                              lib.SDL_GL_GetProcAddress(bytes(name, 'utf-8')))
Esempio n. 15
0
File: hud.py Progetto: ursi/sbpe
    def onPresent(self):
        if not self.draw:
            return

        x = self.config.x
        y = self.config.y

        # hp
        self.txt_hp.draw(x, y, anchorX=1, anchorY=0.5)
        self.txt_hpmax.draw(x - 4, y, anchorY=0.5)

        # ammo
        y = y + self.txt_hp.h + self.config.spacing
        self.txt_ammo.draw(x, y, anchorX=1, anchorY=0.5)
        self.txt_ammomax.draw(x - 4, y, anchorY=0.5)

        # currency
        y = y + self.txt_ammo.h + self.config.spacing
        self.txt_currency.draw(x, y, anchorX=0.5)

        # hp bar
        wv = self.refs.WorldView
        cw = self.refs.ClientWorld
        player = ffi.cast('struct WorldObject *', cw.player)
        props = player.props
        hp = props.hitpoints
        maxhp = props.maxhitpoints

        width = self.config.bar_width
        if width < 0:
            bw = maxhp
        else:
            bw = width
        bh = self.config.bar_height

        if bw <= 0 or bh <= 0:
            return

        x = props.xmp // 256 + props.wmp // 512 - wv.offset.x
        y = props.ymp // 256 - wv.offset.y

        # window space coords
        x = round(x / self.refs.scaleX)
        y = round(y / self.refs.scaleY)
        bx = x - bw // 2
        by = y - bh - self.config.bar_y
        cw = self.refs.canvasW_[0]
        ch = self.refs.canvasH_[0]
        self.refs.canvasW_[0] = self.refs.windowW
        self.refs.canvasH_[0] = self.refs.windowH

        # outline
        self.refs.XDL_FillRect(bx - 1, by - 1, bw + 2, bh + 2,
                               self.config.bar_outline, lib.BLENDMODE_BLEND)
        # background
        self.refs.XDL_FillRect(bx, by, bw, bh, self.config.bar_background,
                               lib.BLENDMODE_BLEND)
        # bar
        filled = math.ceil(hp * bw / maxhp)
        self.refs.XDL_FillRect(bx, by, filled, bh, self.config.bar_color,
                               lib.BLENDMODE_BLEND)
        # notches
        st = 1
        while True:
            cx = bx + round(bw * (maxhp - st * 25) / maxhp)
            if cx <= bx + filled:
                break
            self.refs.XDL_FillRect(cx, by, 1, bh, self.config.bar_notches,
                                   lib.BLENDMODE_BLEND)
            st += 1

        # restore coords
        self.refs.canvasW_[0] = cw
        self.refs.canvasH_[0] = ch
Esempio n. 16
0
    def onPresent(self):
        if not self._initedopts:
            self._initedopts = True
            for k in TARGETS.keys():
                for opt in OPTS:
                    self.config.option(
                        'arrow_{}_{}'.format(k, opt[0]),
                        self.config['arrows_' + opt[0]], opt[2])

        cw = self.refs.ClientWorld
        wv = self.refs.WorldView
        if cw == ffi.NULL or wv == ffi.NULL:
            return

        plr = cw.player
        if plr == ffi.NULL:
            return
        if util.getClassName(plr) not in self.refs.CASTABLE['PlayerCharacter']:
            return
        plr = ffi.cast('struct PlayerCharacter *', plr)

        objects = util.worldobjects(cw.serverSubWorld)
        objects += util.worldobjects(cw.mySubWorld.asNativeSubWorld)
        objects += util.vec2list(cw.allies, 'struct WorldObject *')

        kinds = self.config.arrows.split()

        for obj in objects:
            p = obj.props
            vid = util.getstr(p.vid)

            # invisible
            if len(vid) == 0:
                # triggers
                if p.trigger != 0:
                    self.drawFrame(
                        obj, self.config.trigger_color,
                        self.config.trigger_frame)
                # otherwise skip
                continue

            w2 = p.wmp // 512
            h2 = p.hmp // 512
            x = p.xmp // 256 + w2 - wv.offset.x
            y = p.ymp // 256 + h2 - wv.offset.y
            inbounds = x + w2 > 0 and y + h2 > 0 and\
                x - w2 < self.refs.canvasW_[0] and\
                y - h2 < self.refs.canvasH_[0]

            # object hp/armor
            if inbounds and self.config.show_hp:
                if p.hitpoints >= 0:
                    self.numbers.draw(
                        p.hitpoints, x, y, anchorX=0.5, anchorY=1)
                elif p.hitpoints != -1:
                    self.negnumbers.draw(
                        abs(p.hitpoints), x, y, anchorX=0.5, anchorY=1)

                if p.armor > 0:
                    self.numbers.draw(p.armor, x, y, anchorX=0.5, anchorY=0)

            # use counts
            if inbounds and self.config.show_uses and p.interact != 0:
                idesc = p.interactdescription
                if idesc != ffi.NULL and idesc.numused > 0 and\
                        idesc.totaluses > 0:
                    self.numbers.draw(
                        idesc.numused, x, y, anchorX=0.5, anchorY=0.5)

            # boosts
            if 'boost' in kinds and vid[:-1] in BOOSTS:
                btype = BOOSTS[vid[:-1]]
                blevel = int(vid[-1])

                # current value: StatVal from PlayerCharacter
                currvalue = getattr(plr, btype[0]).base

                # max values list: RepeatedField_int from CharacterDescription
                mvals = getattr(plr.charDesc, btype[1])
                mvals = ffi.unpack(mvals.elements, mvals.current_size)

                for i in range(1, len(mvals)):
                    if mvals[i] > currvalue and i <= blevel:
                        self.drawArrow(plr, obj, 'boost')
                        break

                continue

            # match vid name with target list
            try:
                k = TVIDMAP[vid]
            except KeyError:
                continue

            # consider only enabled types
            if k not in kinds:
                continue

            # do we need hp?
            if k == 'hp':
                plrprops = ffi.cast('struct WorldObject *', plr).props
                if plrprops.hitpoints == plrprops.maxhitpoints:
                    continue

            # all checks passed, we are interested in this obj
            self.drawArrow(plr, obj, k)

        # zone/room id
        if self.config.show_room_id:
            cwprops = cw.asWorld.props
            txt = util.getstr(cwprops.zone) or util.getstr(cwprops.music)
            if cwprops.floor > 0:
                txt = '{} {}'.format(txt, cwprops.floor + 1)
            self.roomtxt.text = txt
            self.roomtxt.draw(
                self.refs.windowW - 4, self.refs.windowH - 4,
                anchorX=1, anchorY=1)
Esempio n. 17
0
    def drawArrow(self, src, dst, kind):
        optprefix = 'arrow_' + kind + '_'

        offset = self.refs.WorldView.offset
        cw = self.refs.canvasW_[0]
        ch = self.refs.canvasH_[0]

        sp = ffi.cast('struct WorldObject *', src).props
        dp = ffi.cast('struct WorldObject *', dst).props

        dw2 = dp.wmp // 512
        dh2 = dp.hmp // 512
        x1 = (dp.xmp) // 256 + dw2 - offset.x
        y1 = (dp.ymp) // 256 + dh2 - offset.y

        inbounds = (x1 + dw2 >= 0 and x1 - dw2 <= cw and y1 + dh2 >= 0 and y1 - dh2 <= ch)

        if inbounds and self.config[optprefix + 'frame'] == 0:
            return

        if inbounds:
            x0 = (sp.xmp + sp.wmp // 2) // 256 - offset.x
            y0 = (sp.ymp + sp.hmp // 2) // 256 - offset.y
        else:
            x0 = cw // 2
            y0 = ch // 2

        angle = math.atan2(y1 - y0, x1 - x0)

        # offset target position to the canvas edges if out of bounds
        t = 1
        if x1 < 0 and x0 != x1:
            t = min(t, x0 / (x0 - x1))
        if y1 < 0 and y0 != y1:
            t = min(t, y0 / (y0 - y1))
        if x1 > cw and x0 != x1:
            t = min(t, (cw - x0) / (x1 - x0))
        if y1 > ch and y0 != y1:
            t = min(t, (ch - y0) / (y1 - y0))
        # arrow point coords
        ax = x0 + t * (x1 - x0)
        ay = y0 + t * (y1 - y0)

        # fading
        dist = math.sqrt((ax - x0) ** 2 + (ay - y0) ** 2)
        fade = self.config[optprefix + 'fade']
        color = self.config[optprefix + 'color']
        f = 1
        if dist < fade and fade > 0:
            f = dist / fade
        # blinking
        bp = self.config[optprefix + 'blink']
        if bp > 0:
            f = f * abs(time.perf_counter() % bp - bp / 2) * 2 / bp

        if f < 1:
            ca = math.floor((color >> 24) * f) & 0xff
            color = (color & 0xffffff) | (ca << 24)

        # draw frame
        if inbounds:
            self.drawFrame(dst, color, self.config[optprefix + 'frame'])
            return

        # draw triangle
        dx = self.config[optprefix + 'length'] * self.refs.scaleX
        dy = self.config[optprefix + 'width'] / 2 * self.refs.scaleX

        # outline
        out = self.config[optprefix + 'outline'] * self.refs.scaleX
        if out > 0:
            offx = out / math.tan(math.pi / 4 - math.atan2(dx, dy) / 2)
            offy = out / math.tan(math.pi / 4 - math.atan2(dy, dx) / 2)
            (oax, oay) = rotate(offx, 0, angle)
            (obx, oby) = rotate(-dx - out, dy + offy, angle)
            (ocx, ocy) = rotate(-dx - out, -dy - offy, angle)
            self.refs.XDL_FillTri(
                round(ax + oax), round(ay + oay),
                round(ax + obx), round(ay + oby),
                round(ax + ocx), round(ay + ocy),
                color & 0xff000000, lib.BLENDMODE_BLEND)

        # middle
        (bx, by) = rotate(-dx, dy, angle)
        (cx, cy) = rotate(-dx, -dy, angle)
        self.refs.XDL_FillTri(
            round(ax), round(ay),
            round(ax + bx), round(ay + by),
            round(ax + cx), round(ay + cy),
            color, lib.BLENDMODE_BLEND)
Esempio n. 18
0
File: hud.py Progetto: ursi/sbpe
 def __del__(self):
     wc = self.refs.WorldClient
     if wc != ffi.NULL and wc.hud != ffi.NULL:
         if wc.hud.hudStatus != ffi.NULL:
             ffi.cast('struct UIElement *', wc.hud.hudStatus).show = True