Page 1 of 1

Help with Drag and Drop Inventory System

Posted: Mon Aug 31, 2015 12:27 am
by Counter Arts
So yeah. Can anyone help me with this?

Here is most of a test case of an inventory system. However I can't seem to get rid of this bug where one of the items swaps places with the one that's currently being dragged.

Code: Select all

# You can place the script of your game in this file.

# Declare images below this line, using the image statement.
# eg. image eileen happy = "eileen_happy.png"

# Declare characters used by this game.
define e = Character('Eileen', color="#c8ffc8")

init python:

    import inspect

    def testDrop(target, sourceDrop):
        print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)

    def returnDrop(xPos,yPos):

        def innerReturnDrop(sourceDrop, target ):
            if target == None:
                return None
            sourceDrop[0].snap(xPos, yPos, delay=10)
            print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)
            renpy.restart_interaction
        return innerReturnDrop


    def resetIndexLocation(sourceWidget, newList, originalItem, newBoxX, newBoxY):
        num = newList.index(originalItem)
        xLocation = newBoxX + (num % 2) * (54 + 60)
        yLocation = newBoxY + (num // 2) * 54
        sourceWidget.snap(xLocation, yLocation)


    def giveItem(sourceList, theItem, originalX, originalY):

        def innerGiveItem(sourceDrop, target):
            global inventory1, inventory2
            if target == None:

                sourceDrop[0].snap(originalX, originalY)
            else:
                if target.drag_name == "Inv1":
                    if sourceList == inventory1:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory1.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory1, theItem, 25, 305)

                elif target.drag_name == "Inv2":
                    if sourceList == inventory2:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory2.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory2, theItem,355, 305)
                else:
                    pass
                    #sourceDrop[0].snap(originalX, originalY)
            renpy.restart_interaction()
        return innerGiveItem



transform testTransform:

    on show:
        alpha 0.0
        linear 1.0 alpha 1.0
    on hide:
        alpha 1.0
        linear 1.0 alpha 0.0

screen innerTestScreen(viewId):

    side "c r":
        viewport id viewId:
            has vbox
            for i in xrange(1,30):
                text "hi {0}".format(i)

        vbar:
            value YScrollValue(viewId)

screen testScreen:
    $ print "inventory1 = {0}".format(inventory1)
    $ print "inventory2 = {0}\n".format(inventory2)
    textbutton "restart Interaction":
        xalign 1.0
        yalign 1.0

        action Return()

    frame:
        area (0,0, 200, 200)
        at testTransform
        use innerTestScreen("viewportA")

    frame:
        area (250, 0, 450, 200)
        at testTransform
        use innerTestScreen("viewportB")

    draggroup:
        drag:
            drag_name "Inv1"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 20 ypos 300 xsize 200 ysize 300

        drag:
            drag_name "Inv2"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 350 ypos 300 xsize 200 ysize 300

        $ startX, startY = (25, 305)
        for num,i in enumerate(inventory1):
            $ xLocation = startX + (num % 2) * (54 + 60)
            $ yLocation = startY + (num // 2) * 54
            drag id "{0}-{1}".format(num,i):
                child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
                droppable False
                dragged giveItem(inventory1, i, xLocation, yLocation)
                xpos xLocation ypos yLocation

        $ startX, startY = (355, 305)
        for num,i in enumerate(inventory2):
            $ xLocation = startX + (num % 2) * (54 + 60)
            $ yLocation = startY + (num // 2) * 54
            drag id "{0}-{1}".format(num,i):
                child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
                droppable False
                dragged giveItem(inventory2, i, xLocation, yLocation)
                xpos xLocation ypos yLocation







# The game starts here.
label start:

    python:
        inventory1 = ["a", "c", "d", "m", "fark"]
        inventory2 = ["b"]


    e "You've created a new Ren'Py game."

    call screen testScreen

    e "Once you add a story, pictures, and music, you can release it to the world!"

    return


Re: Help with Drag and Drop Inventory System

Posted: Mon Aug 31, 2015 5:51 pm
by orz
I'm a bit baffled it's even half working.

Here's an immediate problem I noticed:

Code: Select all

dragged giveItem(inventory2, i, xLocation, yLocation)
This probably isn't doing what you think it's doing. Instead of firing off that specific code when it's dragged... when the screen is run, that code is being evaluated immediately. It's just due to how Renpy is.

So, what it's expecting is a location of a function, not a function call itself. That is, it's wanting giveItem, not giveItem(inventory2, [...]) etc etc. This is, of course, a slight problem because you want to pass along more parameters than the two it automatically does (which is the list of drags being dragged, and whatever it might be dropped upon).

So, what you can do is have something evaluate that'll just return a function's location, or anonymously bind it with a lambda. For option A, just use renpy.curry like such:

Code: Select all

init python:
    curried_giveItem = renpy.curry(giveItem)

### and wherever your drag is...
drag:
    dragged curried_giveItem(inventory2, i, xLocation, yLocation) ## so this, when it evaluates, will give back the giveItem function,
## which is *then* used by dragged
Or with lambda, which I'm still not that great at using...
I tried typing up an example but I realized I'm still not that sure how to use it! Oh well.

Re: Help with Drag and Drop Inventory System

Posted: Mon Aug 31, 2015 11:17 pm
by Counter Arts
@orz

About that... I was just abusing closures which kinda does the same thing. In the outer function you define the inner function. Then you return the inner function which is a callable.

Unless I am mistaken... maybe I should use the renpy.curry or the python functools.

Re: Help with Drag and Drop Inventory System

Posted: Tue Sep 01, 2015 9:11 am
by orz
About that... I was just abusing closures which kinda does the same thing.
Aha, so you were. I missed that in my first read-through.

Well then. I'll have to run it myself (which involves editing out the graphics).

Re: Help with Drag and Drop Inventory System

Posted: Tue Sep 01, 2015 3:23 pm
by orz
I pared it down and ran through it a number of times (using 'watch inventory1' and 'watch inventory2' in console was most useful), and my best guess, and this is a guess, is that because you're building the positions from a list going to the same positions, when the interaction is restarted, Renpy is erroneously believing that the wrong displayable is the one you were using and is attempting to reuse it there. This has to do with Displayable Reuse, which is under Screen Optimization in the documentation.

For example, if you were dragging the drag associated with the third index over to the other inventory, it gets removed from the third index and added to the other list just fine. Then Renpy, when it redraws the screen comes to the "new" third index which has the same (starting) position as the "old" third index, it thinks it's the same third index and places it right back under your cursor.

How to fix it? I'm not sure, off hand.

Re: Help with Drag and Drop Inventory System

Posted: Tue Sep 01, 2015 4:54 pm
by Counter Arts
@orz

You are not going to like the fix. It's based off an old trick to break ties for sorting algorithms that required no ties.

You ready?

Code: Select all

xoffset (renpy.random.random() * 0.0001)

Re: Help with Drag and Drop Inventory System

Posted: Tue Sep 01, 2015 5:09 pm
by orz
On the contrary, I like it.

I have a thing for coding abominations.

With that said, isn't there a very, very, very small chance you could get the same random() two restarted interactions in a row and still get that error? At that point, wouldn't it just be better to basically do:

Code: Select all

if time_to_offset:
    xoffset 0.00001
$ time_to_offset = not time_to_offset
?

Re: Help with Drag and Drop Inventory System

Posted: Tue Sep 01, 2015 5:24 pm
by Counter Arts
Would that work if there were more than two elements to tie-break?

Re: Help with Drag and Drop Inventory System

Posted: Tue Sep 01, 2015 7:59 pm
by orz

Code: Select all

##outside the screen
init python:
    time_to_offset = False ## or True, doesn't matter

## then, in your screen...

for loops in whatever:
    drag:
        id etcstuff
        if time_to_offset:
             xoffset 0.0001

## do the same with your second for loop

## at the end,
$time_to_offset = not time_to_offset

## and if you want to look at it, add at the end of the screen:
text "[time_to_offset]" pos (0.5,0.2)

Re: Help with Drag and Drop Inventory System

Posted: Wed Sep 02, 2015 5:51 pm
by Counter Arts
So yeah... turns out the previous said solution can cause a memory and savefile leak. Here is another way.

Code: Select all

define e = Character('Eileen', color="#c8ffc8")

init python:

    import inspect

    def testDrop(target, sourceDrop):
        print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)

    def returnDrop(xPos,yPos):

        def innerReturnDrop(sourceDrop, target ):
            if target == None:
                return None
            sourceDrop[0].snap(xPos, yPos, delay=10)
            print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)
            renpy.restart_interaction
        return innerReturnDrop


    def resetIndexLocation(sourceWidget, newList, originalItem, newBoxX, newBoxY, columns=2, itemWidth=114, itemHeight=54, delay=0.5, padding=4):
        print "reset Index called"
        num = newList.index(originalItem)
        xLocation = newBoxX + (num % columns) * (itemWidth +padding) + padding
        yLocation = newBoxY + (num // columns) * (itemHeight +padding) + padding
        sourceWidget.style.xpos = xLocation
        sourceWidget.style.ypos = yLocation
        sourceWidget.snap(xLocation, yLocation, delay)

    def resetAllIndexLocations(currentDrags, allInventoryDefs):
        for i in currentDrags:
            for j in allInventoryDefs:
                if i.dataItem in j[0]:

                    resetIndexLocation(i, j[0], i.dataItem,j[1][0], j[1][1] )


    def giveItem(sourceList, theItem, originalX, originalY):

        def innerGiveItem(sourceDrop, target):
            global inventory1, inventory2
            if target == None:

                sourceDrop[0].snap(originalX, originalY)
            else:
                if target.drag_name == "Inv1":
                    if sourceList == inventory1:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory1.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory1, theItem, 25, 305)

                elif target.drag_name == "Inv2":
                    if sourceList == inventory2:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory2.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory2, theItem,355, 305)
                elif target.drag_name.startswith("item"):
                    inventoryId, dragItem = target.drag_name.split(" ")[1].split("-")
                    targetInventory = inventory1
                    if inventoryId == "2":
                        targetInventory = inventory2
                    theIndex = targetInventory.index(dragItem)
                    sourceList.remove(theItem)
                    targetInventory.insert(theIndex, theItem)
                    print "droped onto {0}".format(dragItem)
                else:
                    pass
                    #sourceDrop[0].snap(originalX, originalY)
            renpy.restart_interaction()
        return innerGiveItem

    def updateInventory(sources, target):
        if target == None:
            return
        print sources[0].dataItem

        resultInventory = None
        sourceDrag = sources[0]


        for i in sourceDrag.dataInventories:
            sourceInventory = None
            if sourceDrag.dataItem in i[0]:
                sourceInventory = i[0]
                sourceInventory.remove(sourceDrag.dataItem)
        for i in sourceDrag.dataInventories:
            if target.inventory is i[0]:
                target.inventory.append(sourceDrag.dataItem)

        for i in sourceDrag.allDrags:
            for j in sourceDrag.dataInventories:
                if i.dataItem in j[0]:

                    resetIndexLocation(i, j[0], i.dataItem,j[1][0], j[1][1] )
                    break

    def generateInventoryDrags(inventorysDefs, itemGen, itemDemensions):
        currentDrags = []
        currentDragGroup = DragGroup(id="inventoryDragGroup", xpos=0, ypos=0, xsize=1.0, ysize=1.0)

        for currentInventory, startLocation, width, height, border, background, itemCol in inventorysDefs:
            print "CI: {0}, startLocation: {1}, width: {2}, height: {3}, border: {4}, background: {5}, itemCol:{6}".format( currentInventory, startLocation, width, height, border, background, itemCol)
            inventoryDrag = Drag(background,xsize=width, ysize=height, xpos=startLocation[0], ypos=startLocation[1],  draggable=False, droppable=True)
            currentDragGroup.add(inventoryDrag)
            inventoryDrag.inventory = currentInventory
            for i in currentInventory:
                dragContents = itemGen(i)
                currentDrag = Drag(dragContents, dragged=updateInventory, drag_name=i, droppable=False)
                currentDrag.dataItem = i
                currentDrag.dataInventories = inventorysDefs
                currentDrag.allDrags = currentDrags
                currentDrags.append(currentDrag)
                currentDragGroup.add(currentDrag)
                resetIndexLocation(currentDrag, currentInventory, currentDrag.dataItem, startLocation[0], startLocation[1] )

        for i in currentDrags:
            for j in i.dataInventories:
                if i.dataItem in j[0]:
                    print "i:{0}, j:{1}".format(i,j)
                    resetIndexLocation(i, j[0], i.dataItem, j[1][0], j[1][1] )
                    break

        return currentDrags, currentDragGroup

    def generateIcon(i):
        return Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)







    inventoryStartLocations = ((25, 305),(355, 305))


label initInventoryDefs:

    python:
        inventoryA = ["ca", "mo"]
        inventoryB = ["kee","mars"]
        iDefs = [
            (inventoryA, (25, 305),300, 300, 4, Frame("dark-window-normal.png", 10, 10), 2),
            (inventoryB, (355, 305), 300, 300, 4,Frame("dark-window-normal.png", 10, 10), 2)
            ]

    return



transform testTransform:

    on show:
        alpha 0.0
        linear 1.0 alpha 1.0
    on hide:
        alpha 1.0
        linear 1.0 alpha 0.0

screen innerTestScreen(viewId):

    side "c r":
        viewport id viewId:
            has vbox
            for i in xrange(1,30):
                text "hi {0}".format(i)

        vbar:
            value YScrollValue(viewId)

screen testScreenTwo:
    #add DragGroup(Drag(Null()))
    default iDrags = generateInventoryDrags(iDefs, generateIcon, (100, 55) )
    add iDrags[1]
    $ resetAllIndexLocations(iDrags[0],iDefs)

screen testScreen:
    textbutton "restart Interaction":
        xalign 1.0
        yalign 1.0

        action Return()

    frame:
        area (0,0, 200, 200)
        at testTransform
        use innerTestScreen("viewportA")

    frame:
        area (250, 0, 450, 200)
        at testTransform
        use innerTestScreen("viewportB")


    draggroup id "testDragGroup":
        drag:
            drag_name "Inv1"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 20 ypos 300 xsize 200 ysize 300

        drag:
            drag_name "Inv2"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 350 ypos 300 xsize 200 ysize 300

        $ print "widget: {0}".format(renpy.get_widget("testScreen", "testDragGroup"))

        for i in masterInventory:
            $ startLocations = inventoryStartLocations[0]
            $ theIndex = 0
            $ targetInventory = inventory1
            $ inventoryName = "1"
            if i in inventory2:
                $ targetInventory = inventory2
                $ startLocations = inventoryStartLocations[1]
                $ theIndex = inventory2.index(i)
                $ inventoryName = "2"
            else:
                $ theIndex = inventory1.index(i)
            $ startX = startLocations[0]
            $ startY = startLocations[1]
            $ xLocation = startX + (theIndex % 2) * (54 + 60)
            $ yLocation = startY + (theIndex // 2) * 54
            drag id ("sa_{0}".format(i)):
                drag_name "item {0}-{1}".format(inventoryName,i)
                child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)

                dragged giveItem(targetInventory, i, xLocation, yLocation)
                xpos xLocation ypos yLocation
                #xoffset (id(i) * 0.000000001)


        #
        # $ startX, startY = (25, 305)
        # for num,i in enumerate(inventory1):
        #     $ xLocation = startX + (num % 2) * (54 + 60)
        #     $ yLocation = startY + (num // 2) * 54
        #     drag id "{1}".format(num,id(i)):
        #         child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
        #         droppable False
        #         dragged giveItem(inventory1, i, xLocation, yLocation)
        #         xpos xLocation ypos yLocation
        #
        # $ startX, startY = (355, 305)
        # for num,i in enumerate(inventory2):
        #     $ xLocation = startX + (num % 2) * (54 + 60)
        #     $ yLocation = startY + (num // 2) * 54
        #     drag id "{1}".format(num,id(i)):
        #         child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
        #         droppable False
        #         dragged giveItem(inventory2, i, xLocation, yLocation)
        #         xpos xLocation ypos yLocation








# The game starts here.
label start:

    call initInventoryDefs

    e "You've created a new Ren'Py game."

    call screen testScreenTwo

    e "Once you add a story, pictures, and music, you can release it to the world!"

    return

Re: Help with Drag and Drop Inventory System

Posted: Wed Sep 02, 2015 8:13 pm
by orz
I'll have to take a look at the code later, but...
So yeah... turns out the previous said solution can cause a memory and savefile leak. Here is another way.
...toggling like that can cause a leak? How?