1. The 30th edition of the Modeling Contest is finally up! The Portable Buildings need your attention, so come along and have a blast!
    Dismiss Notice
  2. The Aftermath has been revealed for the 19th Terraining Contest! Be sure to check out the Results and see what came out of it.
    Dismiss Notice
  3. Melee Mapping Contest #3 - Results are out! Congratulate the winners and check plenty of new 4v4 melee maps designed for this competition!
    Dismiss Notice
  4. The winners of our cinematic soundtrack competition have been decided! Step by the Music Contest #11 - Results to check the entries and congratulate the winners!
    Dismiss Notice

[Wurst] MouseUtils

Discussion in 'The Lab' started by apsyll, Mar 16, 2019 at 10:54 AM.

  1. apsyll

    apsyll

    Joined:
    Aug 28, 2015
    Messages:
    176
    Resources:
    1
    Maps:
    1
    Resources:
    1
    I started creating a mouse control/helper system for my project that uses the mouse as input device heavily.
    It is at this stage working but not fully polished.
    you can check if a player presses a specific button, get the amount of multy clicks (double/tripperl/quadrupel... clicks), the mouse position a drag path of each button and the current mouse event.
    Like always feedback ideas and suggestions are more than welcome.
    credits:

    Frotty
    Chaosy

    changelog

    Split the code in two packages:
    -MouseUtils
    -DragPath
    cleaned up the onMouseEvent function:
    -using a new mousebutton.toKey function to access the currentEvent via hashlist.
    changed multiple if to switches.
    using Players package(FrEntity) to initialize the tuple types for the playing players.

    code

    Code (WurstScript):
    package DragPath
    import LinkedList

    public constant mousePathPointDistance = 1000       // the minimum distance the mouse has to move from its last path point to create a new point

    public tuple clickTuple(int leftClicks, int rightClicks, int middleClicks, DragPath leftPath, DragPath rightPath, DragPath middlePath, real leftCD, real rightCD, real middleCD)

    class DragPoint
        let pathPoint = ZERO3
        construct(real x, real y, real z)
            pathPoint = vec3(x, y, z)
     
        construct(vec2 currentPosition, bool withTerrainZ)
            pathPoint = withTerrainZ ? currentPosition.withTerrainZ() : currentPosition.toVec3()

        construct(vec3 currentPosition)
            pathPoint = currentPosition
     
        function getVector() returns vec3
            return pathPoint

    public class DragPath
        vec2 oldMousePosition
        LinkedList<DragPoint> path = new LinkedList<DragPoint>

        construct(vec2 currentMousePosition)
            path.add(new DragPoint(currentMousePosition, true))
            this.oldMousePosition = currentMousePosition
     
        //returns true if a new point is set
        function onMouseMove(vec2 currentMousePosition) returns bool
            if currentMousePosition.distanceToSq(oldMousePosition) >= mousePathPointDistance
                path.add(new DragPoint(currentMousePosition, true))
                oldMousePosition = currentMousePosition
                return true
            return false

        function getPath() returns LinkedList<DragPoint>
            return this.path

        function getIndexDragPoint(int index) returns DragPoint
            return path.get(index)

        function onButtonRelease(vec2 currentMousePosition)
            path.add(new DragPoint(currentMousePosition, true))
            oldMousePosition = currentMousePosition
     
        ondestroy
            path.clear()
     
    Code (WurstScript):
    package MouseUtils
    import ClosureEvents
    import ClosureTimers
    import DragPath
    import Players
    import HashMap

    public enum MouseCurrentEvent
        MOUSE_PRESSED_LEFT
        Mouse_DRAGGED_LEFT
        MOUSE_RELEASED_LEFT
        MOUSE_PRESSED_RIGHT
        Mouse_DRAGGED_RIGHT
        MOUSE_RELEASED_RIGHT
        MOUSE_PRESSED_MIDDLE
        Mouse_DRAGGED_MIDDLE
        MOUSE_RELEASED_MIDDLE
        MOUSE_MOVED
        MOUSE_STOPPED

    public constant mouseButtonResetTime = 0.2          // the maximum time between two clicks to count as multi click (in seconds)
    public constant mouseMoveResetTime = 1.00 / 24.00   // the time after mouse movement to set current event to stop (in seconds)

    //mouseStatus(the most recent mouse event, is the left button pressed, is the right button pressed, is the middle button pressed, is the mouse moving, the current mouse pointer position on the map, the time before the mouse is counted as standing)
    public function player.getMouseStatus() returns mouseTuple
        return mouseStatuses[this.getId()]

    //clickStatuses(the amount of multi clicks of the left button, the amount of multi clicks of the right button, the amount of multi clicks of the middle button, the path drawn on the map with pressed left button, the path drawn on the map with pressed right button, the path drawn on the map with pressed middle button, time before the multi click amount is reset for the left button, time before the multi click amount is reset for the right button, time before the multi click amount is reset for the middle button)
    public function player.getMouseClicks() returns clickTuple
        return clickStatuses[this.getId()]

    //sets the multi click amount of the given mouse button to the given amount
    public function player.setClickCounter(mousebuttontype wichButton, int count)
        if wichButton == MOUSE_BUTTON_TYPE_LEFT
            clickStatuses[this.getId()].leftClicks = count
        else if wichButton == MOUSE_BUTTON_TYPE_RIGHT
            clickStatuses[this.getId()].rightClicks = count
        else if wichButton == MOUSE_BUTTON_TYPE_MIDDLE
            clickStatuses[this.getId()].middleClicks = count
    //returns the amount of multi clicks of the given mouse button
    public function player.getClickCounter(mousebuttontype wichButton) returns int
        if wichButton == MOUSE_BUTTON_TYPE_LEFT
            return clickStatuses[this.getId()].leftClicks
        else if wichButton == MOUSE_BUTTON_TYPE_RIGHT
            return clickStatuses[this.getId()].rightClicks
        else if wichButton == MOUSE_BUTTON_TYPE_MIDDLE
            return clickStatuses[this.getId()].middleClicks
        return 0
    //returns the mouse position on the map
    public function player.getMousePosition() returns vec2
        return mouseStatuses[this.getId()].mousePosition

    //#############################
    //          Core Code
    //#############################
    public tuple mouseTuple(MouseCurrentEvent currentEvent, boolean left, boolean right, boolean middle, boolean move, vec2 mousePosition, real moveCD)
    mouseTuple array mouseStatuses
    clickTuple array clickStatuses

    public constant currentEventMap = new HashMap<int, MouseCurrentEvent>

    EventListener firstKeyListener = null

    public function addMouseListener(EventListener listener) returns EventListener
        if firstKeyListener != null
            firstKeyListener.prev = listener
            listener.next = firstKeyListener

        firstKeyListener = listener
        return listener

    public function removeMouseListener(EventListener listener)
        if firstKeyListener == listener
            firstKeyListener = null
        destroy listener

    function mouseTuple.updatePath(int playerId)
        var mouseStatus = this
        if this.left
            if clickStatuses[playerId].leftPath.onMouseMove(mouseStatus.mousePosition)
                mouseStatuses[playerId].currentEvent = Mouse_DRAGGED_LEFT
        if this.right
            if clickStatuses[playerId].rightPath.onMouseMove(mouseStatus.mousePosition)
                mouseStatuses[playerId].currentEvent = Mouse_DRAGGED_RIGHT
        if this.middle
            if clickStatuses[playerId].middlePath.onMouseMove(mouseStatus.mousePosition)
                mouseStatuses[playerId].currentEvent = Mouse_DRAGGED_MIDDLE


    function mouseTuple.mousePressed(int playerId)
        switch this.currentEvent
            case MouseCurrentEvent.MOUSE_PRESSED_LEFT
                clickStatuses[playerId].leftClicks ++
                clickStatuses[playerId].leftPath = new DragPath(this.mousePosition)
            case MouseCurrentEvent.MOUSE_PRESSED_RIGHT
                clickStatuses[playerId].rightClicks ++
                clickStatuses[playerId].rightPath = new DragPath(this.mousePosition)
            case MouseCurrentEvent.MOUSE_PRESSED_MIDDLE
                clickStatuses[playerId].middleClicks ++
                clickStatuses[playerId].middlePath = new DragPath(this.mousePosition)
            default

    function mouseTuple.mouseReleased(int playerId)
        DragPath currentPath = null
        switch this.currentEvent
            case MouseCurrentEvent.MOUSE_RELEASED_LEFT
                clickStatuses[playerId].leftCD = mouseButtonResetTime
                currentPath = clickStatuses[playerId].leftPath
            case MouseCurrentEvent.MOUSE_RELEASED_RIGHT
                clickStatuses[playerId].rightCD = mouseButtonResetTime
                currentPath = clickStatuses[playerId].rightPath
            case MouseCurrentEvent.MOUSE_RELEASED_MIDDLE
                clickStatuses[playerId].middleCD = mouseButtonResetTime
                currentPath = clickStatuses[playerId].middlePath
            default
        if currentPath != null
            currentPath.onButtonRelease(this.mousePosition)
            destroy currentPath

    function mousebuttontype.toKey(eventid whichEvent ) returns int
        var i = 0
        if this == MOUSE_BUTTON_TYPE_LEFT
            i = 1
        else if this == MOUSE_BUTTON_TYPE_RIGHT
            i = 2
        else if this == MOUSE_BUTTON_TYPE_MIDDLE
            i = 3  
        if whichEvent == EVENT_PLAYER_MOUSE_UP
            i += 3
        return i

    function mouseResetLoop()
        for p in ALL_PLAYERS
            let i = p.getId()
            var mouseStatus = mouseStatuses[i]
            var clickStatus = clickStatuses[i]
            if mouseStatus.moveCD > 0.00
                if mouseStatus.moveCD <= ANIMATION_PERIOD
                    mouseStatuses[i].moveCD = 0.00
                    mouseStatuses[i].currentEvent = MOUSE_STOPPED
                else
                    mouseStatuses[i].moveCD -= ANIMATION_PERIOD
            if clickStatus.leftCD > 0.00
                if clickStatus.leftCD <= ANIMATION_PERIOD
                    clickStatuses[i].leftClicks = 0
                    clickStatuses[i].leftCD = 0.00
                else
                    clickStatuses[i].leftCD -= ANIMATION_PERIOD
            if clickStatus.rightCD > 0.00
                if clickStatus.rightCD <= ANIMATION_PERIOD
                    clickStatuses[i].rightClicks = 0
                    clickStatuses[i].rightCD = 0.00
                else
                    clickStatuses[i].rightCD -= ANIMATION_PERIOD
            if clickStatus.middleCD > 0.00
                if clickStatus.middleCD <= ANIMATION_PERIOD
                    clickStatuses[i].middleClicks = 0
                    clickStatuses[i].middleCD = 0.00
                else
                    clickStatuses[i].middleCD -= ANIMATION_PERIOD
        doAfter(ANIMATION_PERIOD) ->
            mouseResetLoop()  

    function onMouseEvent()
        let triggerPlayer = GetTriggerPlayer()
        let playerId = triggerPlayer.getId()
        var mouseStatus = mouseStatuses[playerId]
        let id = GetTriggerEventId()
        mouseStatus.mousePosition = vec2(BlzGetTriggerPlayerMouseX(),BlzGetTriggerPlayerMouseY())
        if id == EVENT_PLAYER_MOUSE_MOVE
            mouseStatus.move = true
            mouseStatus.currentEvent = MOUSE_MOVED
            mouseStatus.moveCD = mouseMoveResetTime
            mouseStatus.updatePath(playerId)
        else
            let mb = BlzGetTriggerPlayerMouseButton()
            mouseStatus.currentEvent = currentEventMap.get(mb.toKey(id))
            if mb == MOUSE_BUTTON_TYPE_LEFT
                mouseStatus.left = (id == EVENT_PLAYER_MOUSE_DOWN)
            else if mb == MOUSE_BUTTON_TYPE_RIGHT
                mouseStatus.right = (id == EVENT_PLAYER_MOUSE_DOWN)
            else if mb == MOUSE_BUTTON_TYPE_MIDDLE
                mouseStatus.middle = (id == EVENT_PLAYER_MOUSE_DOWN)
            mouseStatus.mousePressed(playerId)
            mouseStatus.mouseReleased(playerId)
        mouseStatuses[playerId] = mouseStatus
     
        var listener = firstKeyListener
        while listener != null
            listener.onEvent()
            listener = listener.next

    init
        currentEventMap.put(1, MouseCurrentEvent.MOUSE_PRESSED_LEFT)
        currentEventMap.put(2, MouseCurrentEvent.MOUSE_PRESSED_RIGHT)
        currentEventMap.put(3, MouseCurrentEvent.MOUSE_PRESSED_MIDDLE)
        currentEventMap.put(4, MouseCurrentEvent.MOUSE_RELEASED_LEFT)
        currentEventMap.put(5, MouseCurrentEvent.MOUSE_RELEASED_RIGHT)
        currentEventMap.put(6, MouseCurrentEvent.MOUSE_RELEASED_MIDDLE)
        doAfter(1.00) ->
            for p in ALL_PLAYERS
                mouseStatuses[p.getId()] = mouseTuple(MouseCurrentEvent.MOUSE_STOPPED, false, false, false, false, vec2(0., 0.), 0.00)
                clickStatuses[p.getId()] = clickTuple(0, 0, 0, null, null, null, 0.00, 0.00, 0.00)
        EventListener.add(EVENT_PLAYER_MOUSE_DOWN, () -> onMouseEvent())
        EventListener.add(EVENT_PLAYER_MOUSE_UP, () -> onMouseEvent())
        EventListener.add(EVENT_PLAYER_MOUSE_MOVE, () -> onMouseEvent())
        mouseResetLoop()
     
     
    Last edited: Mar 19, 2019 at 6:30 AM
  2. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,498
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    There is way too much copy paste code here.

    if id == EVENT_PLAYER_MOUSE_DOWN
    if mb == MOUSE_BUTTON_TYPE_LEFT
    mouseStatus.currentEvent = MOUSE_PRESSED_LEFT
    mouseStatus.left = true
    else if mb == MOUSE_BUTTON_TYPE_RIGHT
    mouseStatus.currentEvent = MOUSE_PRESSED_RIGHT
    mouseStatus.right = true
    else if mb == MOUSE_BUTTON_TYPE_MIDDLE
    mouseStatus.currentEvent = MOUSE_PRESSED_MIDDLE
    mouseStatus.middle = true
    mouseStatus.mousePressed(playerId)
    else if id == EVENT_PLAYER_MOUSE_UP
    if mb == MOUSE_BUTTON_TYPE_LEFT
    mouseStatus.currentEvent = MOUSE_RELEASED_LEFT
    mouseStatus.left = false
    else if mb == MOUSE_BUTTON_TYPE_RIGHT
    mouseStatus.currentEvent = MOUSE_RELEASED_RIGHT
    mouseStatus.right = false
    else if mb == MOUSE_BUTTON_TYPE_MIDDLE
    mouseStatus.currentEvent = MOUSE_RELEASED_MIDDLE
    mouseStatus.middle = false

    With some hashmap usage you could remove all of these and do something like.

    mouseStatus.currentEvent = hash.get(mb)
    that'd save you quite a few rows as this method can be applied on more than one location in your code.

    Secondly, look into switch cases, they are way cleaner than a lot of else ifs

    I'd also reason that classes should be in their respective files.
     
  3. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,365
    Resources:
    10
    Models:
    3
    Tools:
    1
    Maps:
    4
    Tutorials:
    1
    Wurst:
    1
    Resources:
    10
    You didn't even incorporate all my feedback from your question thread..
     
  4. apsyll

    apsyll

    Joined:
    Aug 28, 2015
    Messages:
    176
    Resources:
    1
    Maps:
    1
    Resources:
    1
    I did this as much as possible sadly you can't use mousebuttontype as key but I just added a small function as a helper.


    I use this system on a map that uses the mouse as main input device due to this I need the possibility to create two paths simultaneously, so I really want to start for each mouse button a path on click and finish it at release.
    I do actually want to use the paths retroactive I just have added the destroy line on release because I don't have written a good alternative yet.

    This sounds actually like a good idea.
    I will keep this in mind and will overthink my approach of how I handle it atm...
    But I like at my system that all paths are now easily accessible via the player, so when I use it later for my systems I can use it "straight out of the box".

    this should be all fixed with the new update please let me know if I forgot something.

    Edit:
    Updated the code.
     
    Last edited: Mar 19, 2019 at 7:02 AM