HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Player Switching

08-07-2009, 09:12 AM#1
Pyrogasm
Okay, so there's already a thread vaguely about this floating around somewhere, but I hoped to raise awareness to this by making a new thread. Basically, I wrote a system that should in theory switch two players in-game.

If I was player 1 and I switched with player 2, all of his units would become mine, my name would change to his, my color would change too, and so would my resources, alliances, etc..

Effectively as if Player 2 and I had stood up and switched computers.

Cool, right? Well it's a little complicated. It involves wrapping GetPlayerId() and Player() so they work with variable PlayerIds, and then there's a whole bunch of stupid stuff that might have to be dealt with. However, here's what I have:

//Old Code removed, see later post...

The problem is that I'm stuck because I can't get 2 people to test this with online, so I'm not sure if any of it actually works. My hope is that if I put this up here, someone can test it for me to see if I went wrong anywhere along the way, or if it actually works as intended.

The other thing is that there are a few PlayerStates that I'm unsure about. In the script there are 6 lines marked with //?, because I'm not actually sure if they're necessary.

I have no idea what PLAYER_STATE_GAME_RESULT means/is for, and I think the food and gold/lumber rates should be updated automatically when the units are switched over, right?


Anyway, it would be nice if someone could test this or give some input on those few lines.
08-07-2009, 01:25 PM#2
Tot
do you use faked ids, or do you really change the players?

Can you make something to switch with an empty position?
(I'm to stupid for that, tried 2 times, 4 fails)

If it works without fakes ids then yours is better than mine
08-07-2009, 02:42 PM#3
Alevice
Quote:
Originally Posted by Pyrogasm
The problem is that I'm stuck because I can't get 2 people to test this with online

*cough*
08-07-2009, 05:18 PM#4
darkwulfv
If this works, it could make for some trippy gameplay stuff.
08-07-2009, 05:23 PM#5
Troll-Brain
Even if it works it can fail in many cases, if you use variable instead of Player() for example.
But i guess explain it in the documentation is enough fine.
08-07-2009, 07:05 PM#6
snowtiger
I didn't read the whole script, but one thing that popped into my mind was: What about researches? I thought it was impossible to "unresearch" something.
08-09-2009, 07:50 AM#7
Pyrogasm
Yeah, I didn't think about techtree research or anything... Although I suppose that if you also triggered your upgrades too it would work.

This calls for a companion library! And besides, upgrades need a decent library anyway.

Tot, the only way to do this is is with "faked" Ids; there are two imortant wrappers in this system: GetPlayerIdEx() and PlayerEx().

Alevice, yeah I forgot about wc3cr whilst writing this...
08-10-2009, 08:34 AM#8
Pyrogasm
Alright, so I made a fair amount of changes. I added comments, fixed anything weird that would have happened with SetPlayerAbilityAvailable(), and I hope to get a standalone Upgrade simulation library working.

Here's the current version:
Collapse JASS:
library PlayerSwitch initializer Init uses BoolexprUtils, LinkedList
    //================================================================
    // PlayerSwitch script by Pyrogasm - Version 1.1 - August 9, 2009
    //================================================================
    //
    // The function of this script is to switch two players in-game.
    // Imagine that you and Player X are sitting at two different computers;
    // you then both stand up and walk to the other's computer and sit down.
    //
    // That is effectively what this script does, except you can switch
    // with any player in the game (not just your buddy next to you, and
    // you don't have to move :)
    //
    // There are a few side-effects:
    //  - You can no longer use Player() GetPlayerId() and
    //    SetPlayerAbilityAvailable(); instead, each has its own
    //    "...Ex()" version that you can use instead with no loss in
    //    functionality.
    //
    //  - Upgrades are currently broken because you can't un-upgrade something
    //    This really shouldn't be that much of a problem for most mapmakers;
    //    however I am planning on writing a companion library to simulate 
    //    upgrades using units so that more functionality may exist.
    //
    //    Should such a library be written, I will appropriately update this
    //    library.
    //
    //  - I'm not entirely sure if this all works properly, or even what some
    //    of it does (See the lines marked with "//?"), and there may be
    //    things other than upgrades I haven't thought of yet that are non-
    //    transferrable. If you think of something, bring it to my attention
    //    at [url]http://www.Wc3c.net/member.php?u=747681[/url] (send me a PM)
    //
    // Usage:
    //  - Use PlayerEx(), GetPlayerIdEx(), and SetPlayerAbilityAvailableEx()
    //    instead of their normal counterparts
    //
    //  - Don't store the owner of a unit in a variable for too long
    //
    //  - Don't use upgrades you don't want transferred (yet)
    //
    //  - Simply call SwitchPlayers(Player1, Player2) and they will switch
    //
    //  - Chill, serve, and enjoy
    //================================================================
    //
    // Thanks to:
    //    - Vexorian, for JASSHelper, and all that jazz
    //    - Mindworx, PitzerMike, Zoxc, Pipedream, and all who have developed
    //      the JASSNewGen Pack at any point
    //    - Ammorth, for his LinkedList script
    //    - Tot, for the inspiration to write this
    //    - Those who have given me critique regarding this
    //
    //================================================================
    // There is no configuration, in case you were wondering.
    //================================================================


    globals
        private keyword PlayerInfo //Some relevant globals
        private PlayerInfo array PIs
        private player Switch

        private integer Team //These are used during the switch
        private playercolor Color
        private string Name
        private integer Id
        private boolean Alliance
        private integer State

        private group G1 //Because creating groups all the time is bad
        private group G2
    endglobals

    private struct PlayerInfo //I could have made it extend an array, but I would have lost the cool [] operator syntax
        player P //This struct stores the relevant data for every player
        integer Id //Player Ids are not static, so they must be saved
        List AList //This list stores which abilities are currently not avaiable to each player

        static method operator [] takes player P returns PlayerInfo
            return PIs[GetPlayerId(P)]
        endmethod

        static method create takes player P, integer J returns PlayerInfo
            local PlayerInfo PI = PlayerInfo.allocate()
            set PI.Id = J
            set PI.P = P
            set PI.AList = List.create()

            return PI
        endmethod

        method onDestroy takes nothing returns nothing //When would this ever be called...?
            call .AList.destroy()
        endmethod
    endstruct


    //Debug functions and hooks to warn against the use of 'deprecated' functions

    debug function GetPlayerIdError takes nothing returns nothing
        debug call BJDebugMsg("|cffff0000Warning: Used GetPlayerId() instead of GetPlayerIdEx()!")
    debug endfunction

    debug function PlayerError takes nothing returns nothing
        debug call BJDebugMsg("|cffff0000Warning: Used Player() instead of PlayerEx()!")
    debug endfunction

    debug function AvailableError takes nothing returns nothing
        debug call BJDebugMsg("|cffff0000Warning: Used SetPlayerAbilityAvailable() instead of SetPlayerAbilityAvailableEx()!")
    debug endfunction

    debug hook GetPlayerId GetPlayerIdError
    debug hook Player PlayerError
    debug hook SetPlayerAbilityAvailable AvailableError


    function SetPlayerAbilityAvailableEx takes player whichPlayer, integer whichAbil, boolean flag returns nothing
        local PlayerInfo PI = PlayerInfo[whichPlayer] //Get the relevant PlayerInfo
        local Link L

        if not flag then //If the ability is to be unavailable
            call Link.create(PI.AList, whichAbil) //Add it to the list of unavailable abilities
            call SetPlayerAbilityAvailable(whichPlayer, whichAbil, false) //And actually make it unavailable
        else
            set L = PI.AList.search(whichAbil) //Make sure it is currently disabled

            if L != 0 then //If it is, then
                call SetPlayerAbilityAvailable(whichPlayer, whichAbil, true) //Enable it
                call L.destroy() //Remove the ability from the list of disabled abilities
            endif
        endif
    endfunction


    function GetPlayerIdEx takes player whichPlayer returns integer
        local PlayerInfo PI = PlayerInfo[whichPlayer] //I couldn't do return PlayerInfo[whichPlayer].Id, for some godforsaken reason
        return PI.Id
    endfunction

    function PlayerEx takes integer whichId returns player
        local integer J = 0
        local PlayerInfo PI

        loop
            set PI = PIs[J] //Iterate through all the PlayerIds
            if PI.Id == whichId then //If the Ids match then return the player
                return PI.P
            endif

            set J = J+1
            exitwhen J >= 12
        endloop

        return null
    endfunction

    private function SwitchUnits takes nothing returns nothing
        call SetUnitOwner(GetEnumUnit(), Switch, true) //Used in SwitchPlayers to change unit ownership
    endfunction

    //Textmacroes make our lives easier and code much cleaner!

    //! textmacro AllianceSet takes ALLIANCE
        set Alliance = GetPlayerAlliance(P1, PI1.P, $ALLIANCE$)
        call SetPlayerAlliance(P1, PI1.P, $ALLIANCE$, GetPlayerAlliance(P2, PI1.P, $ALLIANCE$))
        call SetPlayerAlliance(P2, PI1.P, $ALLIANCE$, Alliance)
    //! endtextmacro

    //! textmacro StateSet takes STATE
        set State = GetPlayerState(P1, $STATE$)
        call SetPlayerState(P1, $STATE$, GetPlayerState(P2, $STATE$))
        call SetPlayerState(P2, $STATE$, State)
    //! endtextmacro

    function SwitchPlayers takes player P1, player P2 returns boolean
        local integer J = 0
        local PlayerInfo PI1
        local PlayerInfo PI2
        local Link L
        local List SwapL

        if P1 == null or P2 == null then //Why would you switch with a null player?
            return false
        endif

        loop
            set PI1 = PIs[J] //Loop through all players and then set the appropriate alliance properties for each of the switching players

            //! runtextmacro AllianceSet("ALLIANCE_PASSIVE")
            //! runtextmacro AllianceSet("ALLIANCE_HELP_REQUEST")
            //! runtextmacro AllianceSet("ALLIANCE_HELP_RESPONSE")
            //! runtextmacro AllianceSet("ALLIANCE_SHARED_XP")
            //! runtextmacro AllianceSet("ALLIANCE_SHARED_VISION")
            //! runtextmacro AllianceSet("ALLIANCE_SHARED_CONTROL")
            //! runtextmacro AllianceSet("ALLIANCE_SHARED_ADVANCED_CONTROL")
            //! runtextmacro AllianceSet("ALLIANCE_RESCUABLE")
            //! runtextmacro AllianceSet("ALLIANCE_SHARED_VISION_FORCED")

            set J = J+1
            exitwhen J >= 16
        endloop

        //This is where the playerstates are set, but I'm not really sure what some of them do
        //Or even if they need to be set

//?        //! runtextmacro StateSet("PLAYER_STATE_GAME_RESULT")
        //! runtextmacro StateSet("PLAYER_STATE_RESOURCE_GOLD")
        //! runtextmacro StateSet("PLAYER_STATE_RESOURCE_LUMBER")
        //! runtextmacro StateSet("PLAYER_STATE_RESOURCE_HERO_TOKENS")
//?        //! runtextmacro StateSet("PLAYER_STATE_RESOURCE_FOOD_CAP")
//?        //! runtextmacro StateSet("PLAYER_STATE_RESOURCE_FOOD_USED")
//?        //! runtextmacro StateSet("PLAYER_STATE_FOOD_CAP_CEILING")
        //! runtextmacro StateSet("PLAYER_STATE_GIVES_BOUNTY")
        //! runtextmacro StateSet("PLAYER_STATE_ALLIED_VICTORY")
        //! runtextmacro StateSet("PLAYER_STATE_PLACED")
        //! runtextmacro StateSet("PLAYER_STATE_OBSERVER_ON_DEATH")
        //! runtextmacro StateSet("PLAYER_STATE_UNFOLLOWABLE")
//?        //! runtextmacro StateSet("PLAYER_STATE_GOLD_UPKEEP_RATE")
//?        //! runtextmacro StateSet("PLAYER_STATE_LUMBER_UPKEEP_RATE")
        //! runtextmacro StateSet("PLAYER_STATE_GOLD_GATHERED")
        //! runtextmacro StateSet("PLAYER_STATE_LUMBER_GATHERED")
        //! runtextmacro StateSet("PLAYER_STATE_NO_CREEP_SLEEP")

        set PI1 = PlayerInfo[P1]
        set PI2 = PlayerInfo[P2]

        set Team = GetPlayerTeam(P1) //These need to be stored so they can be switched later
        set Color = GetPlayerColor(P1)
        set Name = GetPlayerName(P1)
        set Id = PI1.Id

        call SetPlayerTeam(P1, GetPlayerTeam(P2))
        call SetPlayerColor(P1, GetPlayerColor(P2))
        call SetPlayerName(P1, GetPlayerName(P2))
        set PI1.Id = PI2.Id

        call SetPlayerTeam(P2, Team) //See?
        call SetPlayerColor(P2, Color)
        call SetPlayerName(P2, Name)
        set PI2.Id = Id

        //Now abilities need to be enabled/disabled properly for both players so nothing stupid happens
        //It's just a simple loop through the list ending when they both end

        set L = PI1.AList.first
        loop
            exitwhen L == 0

            call SetPlayerAbilityAvailable(P2, L.data, false) //Simple switching
            call SetPlayerAbilityAvailable(P1, L.data, true)
            set L = L.next
        endloop

        set L = PI2.AList.first
        loop
            exitwhen L == 0

            call SetPlayerAbilityAvailable(P1, L.data, false)
            call SetPlayerAbilityAvailable(P2, L.data, true)
            set L = L.next
        endloop

        set SwapL = PI1.AList //I could clone the lists or something stupid, but instead I'll just switch them
        set PI1.AList = PI2.AList
        set PI2.AList = SwapL

        call GroupEnumUnitsOfPlayer(G1, P1, BOOLEXPR_TRUE) //"OfPlayer" loops get locust units and all that jazz too
        call GroupEnumUnitsOfPlayer(G2, P2, BOOLEXPR_TRUE)

        set Switch = P2
        call ForGroup(G1, function SwitchUnits) //Unit switching for both players
        set Switch = P1 //We need a second group so all the units don't end up in the control of the second player
        call ForGroup(G2, function SwitchUnits)
        call GroupClear(G1)
        call GroupClear(G2)

        return true
    endfunction


    private function Init takes nothing returns nothing
        local integer J = 0

        loop
            set PIs[J] = PlayerInfo.create(Player(J), J) //Create all of the necessary PlayerInfos
            set J = J+1
            exitwhen J >= 12
        endloop

        set G1 = CreateGroup() //Just initialization setting
        set G2 = CreateGroup()
    endfunction
endlibrary

And I still am looking for someone to shed light on the //? lines....
08-10-2009, 01:04 PM#9
Tot
Quote:
// - Upgrades are currently broken because you can't un-upgrade something
// This really shouldn't be that much of a problem for most mapmakers;
// however I am planning on writing a companion library to simulate
// upgrades using units so that more functionality may exist.


why not gating upgrades with negative upgrades
eg.
upgrade +a increases AS by 1
upgrade -a increases AS by -1

Only prob if upgrades can't be created via triggers:
userr has to define the -upgrades self (could be quite bad (at least you're an upgrade-fanatic like me))
08-13-2009, 07:29 PM#10
Troll-Brain
I think you doesn't need to set PLAYER_STATE_GOLD_UPKEEP_RATE , PLAYER_STATE_LUMBER_UPKEEP_RATE, coz when you will switch the owners of units it would be done naturally, and anyway i think theses constant can only be used to read the value not to set them. (need to test it though)

PLAYER_STATE_GAME_RESULT can be setted if you can add some negative score in some way, however i dunno if it's possible.