• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Multiplayer issue with MouseUtil + additional scripts

Status
Not open for further replies.
First off: This is might require some code reading to solve, I think, and possibly a fair amount of it.
I hope that someone can help me.

The Issue
My map shoots projectiles towards the mouse-location and it seems to work fine in single player.
When I go multiplayer (or technically always, I guess), it uses the location of the player who moved its mouse last for all players, resulting in hard-to-impossible to do what you expect in the correct direction for all players.

The details
The map is Mecha Protectors I use [vJASS] - [Snippet] Mouse Utility. There is only 1 unit (per player) for players and you have locked camera and cannot deselect it. There is a dash-ability.
With the Mouse utility (or blizzards API actually), it doesn't fire updates unless you move the mouse.
So if you hold your mouse close to your hero, let go of the mouse and dash (towards the mouse location), you would dash in that direction as expected. However, if you would dash again (still haven't touched the mouse), you would dash backwards (towards the last registered world-location) instead of the same direction again (as the mouse-location suggests).
1677437196820.png

As an image, the same thing:
You start at blue circle 1 with mouse-location at green square 1, if you would shoot, you'd fire in the red-direction, if you would dash to blue circle 2, the mouse world location would be green 2, but you would fire in the red direction, not towards the green 2, because you haven't fired any mouse-move-events!

The solution:
Keep track of the relative position and when you need the mouse-location, do a new relative-to-world calculation:

Of course, I have had prints that ensures that this and player number values are as expected here.

JASS:
library MouseUtilsLockCamExtension requires MouseUtils
    struct LockCamExtension extends array
        private unit u
        private real relativeX
        private real relativeY
        static method get takes player p returns thistype
            return thistype(GetPlayerId(p))
        endmethod
        static method onMouseMovement takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local thistype this   = get(p)
            set .relativeX = GetPlayerMouseX(p) - GetUnitX(u)
            set .relativeY = GetPlayerMouseY(p) - GetUnitY(u)
            //call BJDebugMsg("OnMouseMovement: " + I2S(GetPlayerId(p)) + ", this=" + I2S(this) + ", x=" + R2S(.relativeX))
            set p = null
        endmethod
        static method registerUnit takes unit u returns nothing
            local thistype this = get(GetOwningPlayer(u))
            //call BJDebugMsg("Registering for: " + I2S(GetPlayerId(GetOwningPlayer(u))) + ", " + GetUnitName(u))
            set .u = u
        endmethod
        method mx takes nothing returns real
            return GetUnitX(.u) + .relativeX
        endmethod
        method my takes nothing returns real
            return GetUnitY(.u) + .relativeY
        endmethod
    endstruct
    function registerLockCamUnit takes unit u returns nothing
        call LockCamExtension.registerUnit(u)
    endfunction
    function initLockCamExtension takes nothing returns nothing
        call UserMouse.registerCode(function LockCamExtension.onMouseMovement, EVENT_MOUSE_MOVE)
    endfunction
    function GetMouseX takes player p returns real
        //call BJDebugMsg("GetMouseX: " + I2S(GetPlayerId(p)))
        return LockCamExtension.get(p).mx()
    endfunction
    function GetMouseY takes player p returns real
        return LockCamExtension.get(p).my()
    endfunction
endlibrary
At start-up register the unit and the onMouseMovement-function to the MouseUtil, track the unit-to-mouse location. When you want the world-position, just add the unit-location to the unit-to-mouse location and BANG, you have it!
Except for this multiplayer issue.

Other, possibly relevant scripts are the custom action system, that requests the mouse-position:
JASS:
library MetroidvaniaController initializer onInit requires MouseUtilsLockCamExtension, MetroidvaniaUtils, MetroidvaniaUi
    globals
        constant oskeytype MC_KEY_ATTACK = OSKEY_Q
        constant oskeytype MC_KEY_ATTACK_ALT = OSKEY_W
        constant oskeytype MC_KEY_UTIL_1 = OSKEY_E
        constant oskeytype MC_KEY_UTIL_2 = OSKEY_R
        constant oskeytype MC_KEY_DASH = OSKEY_D
        constant oskeytype MC_KEY_INTERACT = OSKEY_F
        constant oskeytype MC_KEY_MAP = OSKEY_M
        constant integer NO_ACTION = -1
        constant integer ACITON_STATE_READY = 1
        constant integer ACTION_STATE_DOING = 2
        constant integer ACTION_STATE_BACKSWING = 3
        constant integer ACTION_STATE_COOLDOWN = 4
        //constant integer UI_ATTACK = 0
        //constant integer UI_ATTACK_ALT = 1
        //constant integer UI_UTIL_1 = 2
        //constant integer UI_UTIL_2 = 3
        //constant integer UI_DASH = 4
        //constant integer UI_PASSIVE_1 = 5
        //constant integer UI_PASSIVE_2 = 6
        //constant integer UI_INTERACT = 7
        //constant integer UI_MAP = 8
    endglobals
    private keyword controllerCallback
    private interface ActionInterface
        method onStart takes nothing returns nothing defaults nothing
        method onActionPoint takes nothing returns nothing defaults nothing
        method onBackswing takes nothing returns nothing defaults nothing
        method onRelease takes nothing returns nothing defaults nothing
        method onCooldownFinished takes nothing returns nothing defaults nothing
        method animationSpeed takes nothing returns real defaults 1.0
        method abilityText takes nothing returns string defaults "TODO"
        method abilityName takes nothing returns string defaults "TODO"
        //method updateCooldown takes nothing returns nothing defaults nothing
    endinterface
    
    struct CooldownDetails extends array
        implement Alloc
        readonly real actionPoint
        readonly real actionDuration
        readonly real actionCooldown
        
        public static method create takes real actionPoint, real actionDuration, real actionCooldown returns thistype
            local thistype this = thistype.allocate()
            set .actionPoint = actionPoint
            set .actionDuration = actionDuration
            set .actionCooldown = actionCooldown
            return this
        endmethod
        public method printDetails takes nothing returns nothing
            call BJDebugMsg("this=" + I2S(this) + ", AP=" + R2S(actionPoint) + ", Dur="+R2S(actionDuration) + ", Cooldown=" + R2S(actionCooldown))
        endmethod
        public method update takes real actionPoint, real actionDuration, real actionCooldown returns nothing
            set .actionPoint = actionPoint
            set .actionDuration = actionDuration
            set .actionCooldown = actionCooldown
            //call BJDebugMsg("--- Updated cooldown details ---")
            //call printDetails()
        endmethod
        public method destroy takes nothing returns nothing
            call this.deallocate()
        endmethod
    endstruct
    private module ActionFlow
        private static method releaseTimer takes nothing returns nothing
            call ReleaseTimer(GetExpiredTimer())
        endmethod
        private static method updateUi takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real remainingCooldown = TimerGetRemaining(.cooldownTimer)
            if remainingCooldown > 0 then
                if GetLocalPlayer() == GetOwningPlayer(u) then
                    call updateCooldown(uiButtonIndex, .cooldownDetails.actionCooldown, remainingCooldown)
                endif
            else
                call ReleaseTimer(GetExpiredTimer())
                set .cooldownTimer = null
            endif
        endmethod
        private static method cooldownFinished takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            if .onCooldownFinished.exists then
                call .onCooldownFinished()
            endif
            call ReleaseTimer(t)
            set t = null
            set actionState = ACITON_STATE_READY
            call controllerCallback.evaluate(u, this)
        endmethod
        private static method atBackswing takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            if .onBackswing.exists then
                call .onBackswing()
            endif
            call TimerStart(t, cooldownDetails.actionCooldown - cooldownDetails.actionDuration, false, function thistype.cooldownFinished)
            set t = null
            set actionState = ACTION_STATE_COOLDOWN
            call controllerCallback.evaluate(u, this)
        endmethod
        private static method atActionPoint takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            if .onActionPoint.exists then
                call .onActionPoint()
            endif
            call TimerStart(t, cooldownDetails.actionDuration - cooldownDetails.actionPoint, false, function thistype.atBackswing)
            set t = null
            if GetLocalPlayer() == GetOwningPlayer(u) then
                call SelectUnit(u, true)
            endif
            set actionState = ACTION_STATE_BACKSWING
            call controllerCallback.evaluate(u, this)
        endmethod
        method start takes nothing returns nothing
            call ConsumeMana(u, energyCost)
            if .onStart.exists then
                call .onStart()
            endif
            set .cooldownTimer = NewTimerEx(this)
            call TimerStart(cooldownTimer, cooldownDetails.actionCooldown, false, function thistype.releaseTimer)
            call TimerStart(NewTimerEx(this), cooldownDetails.actionPoint, false, function thistype.atActionPoint)
            call TimerStart(NewTimerEx(this), 0.02, true, function thistype.updateUi)
            set actionState = ACTION_STATE_DOING
            if cooldownDetails.actionPoint == 0. then
                call controllerCallback.evaluate(u, this)
            elseif GetLocalPlayer() == GetOwningPlayer(u) then
                call ClearSelection()
            endif
        endmethod
    endmodule
    struct HeroActionBase extends ActionInterface
        private integer actionState
        public boolean isHolding
        readonly timer cooldownTimer
        readonly unit u
        readonly CooldownDetails cooldownDetails
        readonly integer uiButtonIndex
        real energyCost
        boolean ignoreTarget
        public method isDoing takes nothing returns boolean
            return actionState == ACTION_STATE_DOING
        endmethod
        public method isBackswing takes nothing returns boolean
            return actionState == ACTION_STATE_BACKSWING
        endmethod
        public method isOnCooldown takes nothing returns boolean
            return actionState != ACITON_STATE_READY
        endmethod
        public method isReady takes nothing returns boolean
            return actionState == ACITON_STATE_READY
        endmethod
        public method isCastable takes nothing returns boolean
            return isReady() and GetMana(u) > energyCost
        endmethod
        method operator owner takes nothing returns player
            return GetOwningPlayer(u)
        endmethod
        stub method updateAbilityDetails takes nothing returns nothing
            call BJDebugMsg("!!! NO UPDATE FUNCTION IMPLEMENTED !!!")
        endmethod
        implement ActionFlow
        public static method create takes unit u, integer uiIndex, string iconPath returns thistype
            local thistype this = thistype.allocate()
            set .u = u
            set .cooldownDetails = CooldownDetails.create(1.0, 1.0, 1.0)
            set .isHolding = false
            set .actionState = ACITON_STATE_READY
            set .uiButtonIndex = uiIndex
            call BlzFrameSetVisible(ui_frame[uiButtonIndex + UI_ABIL_BTN_OFFSET], true)
            call BlzFrameSetVisible(ui_frame[uiButtonIndex + UI_ABIL_ICON_OFFSET], true)
            call BlzFrameSetTexture(ui_frame[uiButtonIndex + UI_ABIL_ICON_OFFSET], iconPath, 0, true)
            call BlzFrameSetValue(ui_frame[uiButtonIndex + UI_ABIL_COOLDOWN_OFFSET], 0)
            return this
        endmethod
        public method destroy takes nothing returns nothing
            if cooldownTimer != null then
                call ReleaseTimer(cooldownTimer)
                set cooldownTimer = null
            endif
            call cooldownDetails.destroy()
            call this.deallocate()
        endmethod
    endstruct
    struct MetroidvaniaController extends array
        private static trigger mc_main_down
        private static trigger mc_main_up
        private static trigger mc_select
        private static trigger mc_deselect
        private unit u
        private unit target
        private boolean isHolding
        private timer t
        private boolean isDoingAction
        private HeroActionBase currentAction
        private HeroActionBase queuedAction
        private real currentActionTargetX
        private real currentActionTargetY
        private HeroActionBase attack
        private HeroActionBase attackAlt
        private HeroActionBase util1
        private HeroActionBase util2
        private HeroActionBase dash
        private HeroActionBase interact
        private HeroActionBase map
        private method keyToAction takes oskeytype key returns HeroActionBase
            if key == MC_KEY_ATTACK then
                return attack
            elseif key == MC_KEY_ATTACK_ALT then
                return attackAlt
            elseif key == MC_KEY_UTIL_1 then
                return util1
            elseif key == MC_KEY_UTIL_2 then
                return util2
            elseif key == MC_KEY_DASH then
                return dash
            elseif key == MC_KEY_INTERACT then
                return interact
            elseif key == MC_KEY_MAP then
                return map
            else
                return NO_ACTION
            endif
        endmethod
        private method updateTargetCoords takes HeroActionBase action returns nothing
            if target != null and UnitAlive(target) and not action.ignoreTarget then
                set .currentActionTargetX = GetUnitX(target)
                set .currentActionTargetY = GetUnitY(target)
            else
                //call BJDebugMsg("updateTargetCoords: Playerid=" + I2S(GetPlayerId(GetOwningPlayer(.u))))
                set .currentActionTargetX = GetMouseX(GetOwningPlayer(.u))
                set .currentActionTargetY = GetMouseY(GetOwningPlayer(.u))
            endif
        endmethod
        public method tryToExecuteAction takes HeroActionBase action returns nothing
            if not isDoingAction and action > 0 and action.isCastable() then
                set currentAction = action
                set isDoingAction = true
                call updateTargetCoords(action)
                call action.start()
            endif
        endmethod
        static method controllerCallback takes unit u, HeroActionBase action returns nothing
            local thistype this = thistype(GetPlayerId(GetOwningPlayer(u)))
            //Should be at start of "backswing" at earliest, allow queueing actions at start of "backswing"
            set isDoingAction = false
            if currentAction == action then
                if currentAction.isHolding and currentAction.isCastable() then
                    call updateTargetCoords(currentAction)
                    call currentAction.start()
                elseif queuedAction != NO_ACTION and queuedAction.isCastable() then
                    call updateTargetCoords(queuedAction)
                    set currentAction = queuedAction
                    call currentAction.start()
                endif
            endif
        endmethod
        private static method keyPressed takes nothing returns nothing
            local thistype this = thistype(GetPlayerId(GetTriggerPlayer()))
            local real tx
            local real ty
            local HeroActionBase action
            if UnitAlive(u) then
                if not isDoingAction then
                    set currentAction = keyToAction(BlzGetTriggerPlayerKey())
                    set queuedAction = NO_ACTION
                    if currentAction != NO_ACTION then
                        //call BJDebugMsg("Trying to cast: " + currentAction.abilityName())
                        set currentAction.isHolding = true
                        call tryToExecuteAction(currentAction)
                    else
                        //call BJDebugMsg("Action is NO_ACTION")
                    endif
                else
                    //call BJDebugMsg("Doing Action...")
                    set action = keyToAction(BlzGetTriggerPlayerKey())
                    if currentAction == action then
                        set currentAction.isHolding = true
                    else
                        set queuedAction = action
                    endif
                endif
            else
                set currentAction = NO_ACTION
                set queuedAction = NO_ACTION
            endif
        endmethod
    
        private static method keyReleased takes nothing returns nothing
            local thistype this = thistype(GetPlayerId(GetTriggerPlayer()))
            local HeroActionBase action = keyToAction(BlzGetTriggerPlayerKey())
            if action != NO_ACTION then
                set action.isHolding = false
                call action.onRelease()
            endif
        endmethod
        private static method selectTargetNearMouse takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local thistype this = thistype(GetPlayerId(p))
            local group g = CreateGroup()
            local unit u
            local real mx = GetMouseX(p)
            local real my = GetMouseY(p)
            local real dx
            local real dy
            local unit selectedUnit = null
            local real distanceSqr = 999999.9
            local real dSqr
            call GroupEnumUnitsInRange(g, mx, my, 80.0, null)
            loop
                set u = FirstOfGroup(g)
                exitwhen u == null
                set dx = GetUnitX(u) - mx
                set dy = GetUnitY(u) - my
                set dSqr = dx * dx + dy * dy
                if .u != u and UnitAlive(u) and dSqr < distanceSqr then
                    set distanceSqr = dSqr
                    set selectedUnit = u
                endif
                call GroupRemoveUnit(g,u)
            endloop
            call DestroyGroup(g)
            set .target = selectedUnit
            if p == GetLocalPlayer() then
                if target == null then
                    call clearUiTarget(this)
                    //call BJDebugMsg("no target")
                else
                    //call BJDebugMsg("Target: " + GetUnitName(target))
                    call setUiTarget(.target)
                endif
            endif
            set selectedUnit = null
            set g = null
            set p = null
        endmethod
        private static method onClick takes nothing returns nothing
            if UserMouse[GetTriggerPlayer()].isMouseButtonClicked(MOUSE_BUTTON_TYPE_LEFT) then
                call selectTargetNearMouse()
            endif
        endmethod
        public method updateAbilityDetails takes nothing returns nothing
            call attack.updateAbilityDetails()
            call attackAlt.updateAbilityDetails()
            call util1.updateAbilityDetails()
            call util2.updateAbilityDetails()
            call dash.updateAbilityDetails()
            call interact.updateAbilityDetails()
            call map.updateAbilityDetails()
        endmethod
        private static method onInit takes nothing returns nothing
            local player p
            local integer i = 0
            local thistype this
            set mc_main_down = CreateTrigger()
            set mc_main_up = CreateTrigger()
            call TriggerAddCondition( mc_main_down, Condition(function thistype.keyPressed))
            call TriggerAddCondition( mc_main_up, Condition(function thistype.keyReleased))
            call OnMouseEvent(function thistype.onClick, EVENT_MOUSE_DOWN)
            loop
                exitwhen i > 4
                set p = Player(i)
                set this = thistype(i)
                set .isDoingAction = false
                set .isHolding = false
                if GetPlayerController(p) == MAP_CONTROL_USER and GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING then
//BlzTriggerRegisterPlayerKeyEvent takes trigger whichTrigger, player whichPlayer, oskeytype key, integer metaKey, boolean keyDown returns event
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_ATTACK, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_ATTACK, 0, false)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_ATTACK_ALT, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_ATTACK_ALT, 0, false)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_UTIL_1, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_UTIL_1, 0, false)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_UTIL_2, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_UTIL_2, 0, false)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_DASH, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_DASH, 0, false)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_INTERACT, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_INTERACT, 0, false)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_down, p, MC_KEY_MAP, 0, true)
                    call BlzTriggerRegisterPlayerKeyEvent(mc_main_up, p, MC_KEY_MAP, 0, false)
                endif
                set i = i + 1
            endloop
            set p = null
        endmethod
        public static method setAttack takes player p, HeroActionBase attack returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .attack = attack
            call updateAbilityDetails()
        endmethod
        public static method setAttackAlt takes player p, HeroActionBase attackAlt returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .attackAlt = attackAlt
            call updateAbilityDetails()
        endmethod
        public static method setUtil1 takes player p, HeroActionBase util1 returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .util1 = util1
            call updateAbilityDetails()
        endmethod
        public static method setUtil2 takes player p, HeroActionBase util2 returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .util2 = util2
            call updateAbilityDetails()
        endmethod
        public static method setDash takes player p, HeroActionBase dash returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .dash = dash
            call updateAbilityDetails()
        endmethod
        public static method setMap takes player p, HeroActionBase map returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .map = map
        endmethod
        public static method setInteract takes player p, HeroActionBase interact returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .interact = interact
        endmethod
        public static method setHero takes player p, unit hero returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .u = hero
        endmethod
        public static method setTarget takes player p, unit target returns nothing
            local thistype this = thistype(GetPlayerId(p))
            set .target = target
        endmethod
        
        public method getAttack takes nothing returns HeroActionBase
            return .attack
        endmethod
        public method getAttackAlt takes nothing returns HeroActionBase
            return .attackAlt
        endmethod
        public method getUtil1 takes nothing returns HeroActionBase
            return .util1
        endmethod
        public method getUtil2 takes nothing returns HeroActionBase
            return .util2
        endmethod
        public method getDash takes nothing returns HeroActionBase
            return .dash
        endmethod
        public method getMap takes nothing returns HeroActionBase
            return .map
        endmethod
        public method getInteract takes nothing returns HeroActionBase
            return .interact
        endmethod
        public method getTargetUnit takes nothing returns unit
            return .target
        endmethod
        public method getHero takes nothing returns unit
            return .u
        endmethod
        public method setTargetUnit takes unit newTarget returns nothing
            set .target = newTarget
        endmethod
        //call MetroidvaniaController.disableControl()
        public static method disableControl takes nothing returns nothing
            call ClearSelection()
        endmethod
        public static method resumeControl takes nothing returns nothing
            call SelectUnit(thistype(GetPlayerId(GetLocalPlayer())).u, true)
        endmethod
        public static method getTargetX takes player p returns real
            //call BJDebugMsg("getTargetX: Playerid=" + I2S(GetPlayerId(p)))
            return thistype(GetPlayerId(p)).currentActionTargetX
        endmethod
        public static method getTargetY takes player p returns real
            return thistype(GetPlayerId(p)).currentActionTargetY
        endmethod
        public static method getMetroidvaniaController takes player p returns MetroidvaniaController
            return thistype(GetPlayerId(p))
        endmethod
    endstruct
    function controllerCallback takes unit u, HeroActionBase action returns nothing
        call MetroidvaniaController.controllerCallback(u, action)
    endfunction
    function GetHero takes player p returns unit
        return MetroidvaniaController.getMetroidvaniaController(p).getHero()
    endfunction
endlibrary

Heroes can have "targets" selected. When firing an attack, you fire towards the target (if you have one) and towards mouse-location otherwise.
The idea is that different heroes can have different abilities on different keys.

In the map (linked), the MouseUtils can be found in ImportedSystems/MouseUtils "trigger", then there are CustomSystems/MouseUtilsLockCamExtension and CustomSystems/HeroController for the Lock-cam solution described, and the Controller that requests the mouse location on bealf of abilities.
Abilities can be found in Hero/HeroAbilities.

I have not been able to replicate this using 2 instances on the same machine, because when alt+tabing to a WC3 instance, you instantly fire a mouse-event when entering the game, so it always "works" when playing LAN on the same computer, because the WC3 instance you're playing is the one that had its mouse-event fire last...

Sorry for the wall of text, but it isn't all that trivial to describe...
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
Would this work?

You cast Dash -> A condition checks to see if you have moved your mouse since the last time you casted Dash. This could be done by comparing old and new x/y coordinates of the Mouse position. The angle/distance between your Hero and the Mouse Pos is then stored in variables. If it turns out that your mouse hasn't moved since your last cast of Dash, the angle/distance that was stored is used instead of the Mouse Pos to determine the direction of the Dash.
 
Would this work?

You cast Dash -> A condition checks to see if you have moved your mouse since the last time you casted Dash. This could be done by comparing old and new x/y coordinates of the Mouse position. The angle/distance between your Hero and the Mouse Pos is then stored in variables. If it turns out that your mouse hasn't moved since your last cast of Dash, the angle/distance that was stored is used instead of the Mouse Pos to determine the direction of the Dash.
That would indeed work, I think, but if a boss has a knockback ability or I would add more movement-abilities, this would also cause the same issues.

The MouseUtilsLockedCamExtension kind of does what you say (Store the mouse-position relative to the unit, when getting, add the unit position to it), but always, but it also have this weird multiplayer bug (I wrote it, so it's my bad). I have a few commented debug-prints in it, but they show the values I expect, not anything strange that would give me an indication to what the problem is...
 
The thing with the mouse-natives is the they should fire events so you can simply use triggering-player to get the player.
The [vJASS] - [Snippet] Mouse Utility saves the last-moved-to-position so you can anytime call a function to get the last-known mouse position.
No need to sync any data (because the natives processes that) and no need to localplayer (because we have triggering player, I.E. the player that moved the mouse).
The MouseUtils seems well-tested and bug-free.
My locked camera extension seemed to follow the patterns from the MouseUtils (that should be bug-free) to handle the locked-camera problems mentioned (but is seemingly buggy).

I could change it from registring this function for all players and let the natives handle sync to doing it myself with local player, but isn't that a strange thing to do?
 
Last edited:
I don't know why this works, but it works:
JASS:
library MouseUtilsLockCamExtension requires MouseUtils
    struct LockCamExtension extends array
        private static unit array lockedCamUnit
        private static real array relativeX
        private static real array relativeY
        static method get takes player p returns integer
            return GetPlayerId(p)
        endmethod
        static method onMouseMovement takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local integer index   = get(p)
            local unit u = lockedCamUnit[index]
            set relativeX[index] = GetPlayerMouseX(p) - GetUnitX(u)
            set relativeY[index] = GetPlayerMouseY(p) - GetUnitY(u)
            //call BJDebugMsg("OnMouseMovement: " + I2S(GetPlayerId(p)) + ", this=" + I2S(this) + ", x=" + R2S(.relativeX))
            set p = null
            set u = null
        endmethod
        static method registerUnit takes unit u returns nothing
            local integer index = get(GetOwningPlayer(u))
            //call BJDebugMsg("Registering for: " + I2S(GetPlayerId(GetOwningPlayer(u))) + ", " + GetUnitName(u))
            set lockedCamUnit[index] = u
        endmethod
        static method mx takes integer index returns real
            return GetUnitX(lockedCamUnit[index]) + relativeX[index]
        endmethod
        static method my takes integer index returns real
            return GetUnitY(lockedCamUnit[index]) + relativeY[index]
        endmethod
    endstruct
    function registerLockCamUnit takes unit u returns nothing
        call LockCamExtension.registerUnit(u)
    endfunction
    function initLockCamExtension takes nothing returns nothing
        call UserMouse.registerCode(function LockCamExtension.onMouseMovement, EVENT_MOUSE_MOVE)
    endfunction
    function GetMouseX takes player p returns real
        //call BJDebugMsg("GetMouseX: " + I2S(GetPlayerId(p)))
        return LockCamExtension.mx(LockCamExtension.get(p))
    endfunction
    function GetMouseY takes player p returns real
        return LockCamExtension.my(LockCamExtension.get(p))
    endfunction
endlibrary

The change is going from an array-struct with variables to "manual arrays" that I index myself.
Maybe I don't understand array-structs as I thought I did?
 
Status
Not open for further replies.
Top