Player Skin Command

Hydra

Helper
Staff member
Helper
Jan 26, 2019
23
6
3
30
Tired of having to organize your skins?
Or do you happen to get this error often when you change your skin and try to relog real quick?:
Invalid session (Try restarting your game)

Well, now you don't even have to log out to change your skin, AND they can be organized by name!

With the Skin command, you can save, set, and rename your skins by name!

Want that famous youtuber's skin without having to skin-grab and apply it? Just do this:
/skin set Skeppy!
Just don't feel like logging out? Here:
/skin save BehrsFancyBlueSuit https://i.imgur.com/6OBXbhH.png - bam, you saved one of my snazzy suits, and you can always just reload it again later with this:
/skin set BehrsFancyBlueSuit!
Oh, and don't worry, if you forgot what you named it, it's built to help you with some dense error-checking, a tab-complete to include existing skin save names, and even a list sub-command to tell you ALL the skins you've saved with /skin list!

Demo Preview: https://streamable.com/1qxdp
Here's the full usage:
/Skin Reset | Resets your skin to your default
/Skin Save PlayerName | Saves a skin by the PlayerName's Skin
/Skin Save MySkinName URL (slim) | Saves a skin by the URL pasted - optionally making it Slim if specified
/Skin Set MySkinName | Sets a skin by the name you saved - if it doesn't exist, you'll get whatever that player's name's skin is!
/Skin List | Lists the skins you've saved
/Skin Delete MySkinName | Deletes a skin by the name you saved

Here's the haste link for the script: https://one.denizenscript.com/haste/68949
Here's the script embedded right here:
Code:
Skin_Command:
    type: command
    name: skin
    debug: false
    description: Manages your player's skin.
    usage: /skin (Reset/Set <&lt>Name<&gt>/Save <&lt>PlayerName<&gt>/Save <&lt>Name<&gt> URL<&gt>/List/Delete <&lt>Name<&gt>
    tab complete:
        - define Arg1 <list[Reset|Set|Save|Delete|List|Rename]>
        - define Arg2 <list[Set|Delete|Rename]>
        - if <context.args.size> == 0:
            - determine <[Arg1]>

    # @ ██ [ /skin Arg ] ██
        - define Whitespace <context.raw_args.ends_with[<&sp>]>
        - else if <context.args.size> == 1 && !<[Whitespace]>:
            - determine <[Arg1].filter[starts_with[<context.args.get[1]>]]>

    # @ ██ [ Does the player even have saved skins to tab complete for? ] ██
        - if !<player.has_flag[SuperSuit_SavedSkins]>:
            - stop
        - define SavedSkins <player.flag[SuperSuit_SavedSkins].parse[before[/]]>

    # @ ██ [ /skin Arg\s ] ██
        - if <context.args.size> == 1 && <[Arg2].contains[<context.args.get[1]>]> && <[Whitespace]>:
            - determine <[SavedSkins]>

    # @ ██ [ /skin Arg\sArg ] ██
        - else if <context.args.size> == 2 && <[Arg2].contains[<context.args.get[1]>]> && !<[Whitespace]>:
            - determine <[SavedSkins].filter[starts_with[<context.args.get[2]>]]>
    syntax:
        - narrate "<&e>Available Sub-Commands: <&a><list[Reset|Set|Save|Delete|List|Rename].separated_by[<&6>, <&a>]>"
        - stop
    script:
    # @ ██ [ Check for args ] ██
        - if <context.args.size> == 0:
            - inject locally syntax instantly

        - define SkinArg <context.args.get[1]>
        - choose <[SkinArg]>:
        # @ ██ [ /skin reset - Resets the player's skin to their own ] ██
            - case Reset:
                - adjust <player> skin:<player.name>
                - narrate "<&a>Skin reset."


        # @ ██ [ /skin set <Name> - Sets the player's skin to the Name they save their skin as ] ██
            - case Set:
                - if <context.args.size> != 2:
                    - narrate "<&e>Available Sub-Commands: <&a><list[Reset|Set|Save|Delete|List|Rename].separated_by[<&6>, <&a>]>"

            # @ ██ [ Check if player saved skin ] ██
                - define SkinName <context.args.get[2]>
                - if <player.has_flag[SuperSuit_SavedSkins]>:
                    - if <player.flag[SuperSuit_SavedSkins].parse[before[/]].contains[<[SkinName]>]>:
                    # @ ██ [ Skin the owner player's skin if skin name not saved ] ██
                        - define SkinBlob:<player.flag[SuperSuit_SavedSkins].map_get[<[SkinName]>]>
                        - adjust <player> skin_blob:<[SkinBlob]>
                        - narrate "<&a>Skin set to: <&e><[SkinName]>"
                        - stop
                - adjust <player> skin:<[SkinName]>
                - narrate "<&a>Skin set to: <&e><[SkinName]>"

        # @ ██ [ /skin save <PlayerName> - Saves a skin by the PlayerName's Skin ] ██
        # @ ██ [ /Skin Save Name URL (slim) | Saves a skin by the URL pasted - optionally making it Slim if specified ] ██
            - case Save:
                - if !<list[2|3|4].contains[<context.args.size>]>:
                    - inject locally syntax instantly

            # @ ██ [ Check if specifying a player's name instead of name&URL ] ██
                - define SkinName <context.args.get[2]>
                - if <context.args.size> == 2:
                    - adjust <player> skin:<[SkinName]>
                    - flag player SuperSuit_SavedSkins:->:<[SkinName]>/<player.skin_blob>
                    - narrate "<&a>Skin saved: <&e><[SkinName]>"
                    - narrate "<&a>Skin set to: <&e><[SkinName]>"
                    - stop

                - define SkinUrl <context.args.get[3]>
                - define SkinModel <context.args.get[4].to_lowercase||empty>

            # @ ██ [ Check if the player already has a skin saved as this name ] ██
                - if <player.has_flag[SuperSuit_SavedSkins]>:
                    - if <player.flag[SuperSuit_SavedSkins].parse[before[/]].contains[<[SkinName]>]>:
                        - narrate "<&c>The specified skin name already exists: <&e><[SkinName]>"
                        - stop

            # @ ██ [ Validate skin model ] ██
                - if !<list[empty|slim].contains[<[SkinModel]>]>:
                    - narrate "<&c>You must specify either empty or slim."
                    - stop

            # @ ██ [ webget skin from api ] ██
                - narrate "<&a>Retrieving the requested skin..."
                - define key <util.random.uuid>
                - run skin_url_task def:<[key]>|<[SkinUrl]>|<[SkinModel]> id:<[key]> instantly
                - while <queue.exists[<[key]>]>:
                    - if <[loop_index]> > 120:
                        - queue <queue[<[key]>]> clear
                        - narrate "<&a>The request timed out. Is the url valid?"
                        - stop
                    - wait 5t
            # $ ██ [ Quick sanity check - ideally this should never be true ] ██
                - if !<server.has_flag[<[key]>]>:
                    - stop
                - if <server.flag[<[key]>]> == null:
                    - narrate "<&a>Failed to retrieve the skin from the provided link. Is the url valid?"
                    - flag server <[key]>:!
                    - stop
                - yaml loadtext:<server.flag[<[key]>]> id:response
                - if !<yaml[response].contains[data.texture]>:
                    - narrate "<&a>An unexpected error occurred while retrieving the skin data. Please try again."
                - else:
                    - define SkinBlob <yaml[response].read[data.texture.value]>;<yaml[response].read[data.texture.signature]>
                - flag server <[key]>:!
                - yaml unload id:response

            # @ ██ [ Save & adjust the player's skin ] ██
                - flag player SuperSuit_SavedSkins:->:<[SkinName]>/<[SkinBlob]>
                - narrate "<&a>Skin saved: <&e><[SkinName]>"
                - adjust <player> Skin_Blob:<[SkinBlob]>
                - narrate "<&a>Skin set to: <&e><[SkinName]>"

        # @ ██ [ /delete <SkinName> -  Deletes the player's saved skin by this name ] ██
            - case Delete:
                - if <context.args.size> != 2:
                    - inject locally syntax instantly

            # @ ██ [ Check if name to delete even exists ] ██
                - define SkinName <context.args.get[2]>
                - if <player.has_flag[SuperSuit_SavedSkins]>:
                    - if !<player.flag[SuperSuit_SavedSkins].parse[before[/]].contains[<[SkinName]>]>:
                        - narrate "<&a>Skin name does not exist: <&e><[SkinName]>"
                        - stop

                - define SkinBlob <player.flag[SuperSuit_SavedSkins].map_get[<[SkinName]>]>
                - flag player SuperSuit_SavedSkins:<-:<[SkinName]>/<[SkinBlob]>
                - narrate "<&a>Skin deleted: <&e><[SkinName]>"

        # @ ██ [ /skin list - Shows you the list of saved skins ] ██
            - case List:
                - if <context.args.size> != 1:
                    - inject locally syntax instantly

            # @ ██ [ Check if player even has skins ] ██
                - if !<player.has_flag[SuperSuit_SavedSkins]>:
                    - narrate "<&c>You have no saved skins."
                    - stop

                - narrate "<&a>Available Skins:"
                - narrate "<&a><player.flag[SuperSuit_SavedSkins].parse[before[/]].separated_by[<&e>, <&a>]>"

        # @ ██ [ /skin rename <OldName> <NewName> - Renames a skin from NameA to NameB ] ██
            - case Rename:
                - if <context.args.size> != 3:
                    - inject locally syntax instantly

            # @ ██ [ Check if player even has skins ] ██
                - if !<player.has_flag[SuperSuit_SavedSkins]>:
                    - narrate "<&c>You have no saved skins."
                    - stop

            # @ ██ [ Check if old skin name is valid ] ██
                - define OldSkinName <context.args.get[2]>
                - if <player.has_flag[SuperSuit_SavedSkins]>:
                    - if !<player.flag[SuperSuit_SavedSkins].parse[before[/]].contains[<[OldSkinName]>]>:
                        - narrate "<&a>You do not have this skin saved: <&e><[OldSkinName]>"
                        - stop

            # @ ██ [ Check if new skin name exists already ] ██
                - define NewSkinName <context.args.get[3]>
                - if <player.has_flag[SuperSuit_SavedSkins]>:
                    - if <player.flag[SuperSuit_SavedSkins].parse[before[/]].contains[<[NewSkinName]>]>:
                        - narrate "<&a>The specified skin name already exists: <&e><[NewSkinName]>"
                        - stop

            # @ ██ [ Swaperonies ] ██
                - define SkinBlob <player.flag[Behrry.Essentials.SavedSkins].map_get[<[OldSkinName]>]>
                - flag player SuperSuit_SavedSkins:->:<[NewSkinName]>/<[SkinBlob]>
                - flag player SuperSuit_SavedSkins:<-:<[OldSkinName]>/<[SkinBlob]>
                - narrate "<&e>Skin<&6>: <&a><[OldSkinName]> <&e>renamed to<&6>: <&a><[NewSkinName]>"
            - default:
                - narrate "<&a>Available Sub-Commands: <&a>Reset<&6>, <&a>Set <&6>(<&a>Name<&6>), <&a>Save <&6>(<&a>url<&6>)"
                - stop
# - Thanks mergu
skin_url_task:
    type: task
    debug: false
    definitions: key|url|model
    script:
        - define req https://api.mineskin.org/generate/url
        - if <[model]> == slim:
            - define req <[req]>?model=slim
        - ~webget <[req]> post:url=<[url]> timeout:30s save:res
        - flag server <[key]>:<entry[res].result||null>

Feel free to @ me in the Denizen Script and Citizens Discord if you have any specific related questions or have an issue to report, I respond to @Behr#5305.

Also, thanks to Mergu for basically doing all of the work writing the webget task used from his script.

My To-Do for this script in the future:
  • A chest-GUI for saved skins with controls for each command
  • A script that separates the sub-commands to their own respective command - just incase people want one or the other
  • Sharing the skins in the form of the skin's head, so you can gift outfits to your less creative friends!
 
  • Like
Reactions: Andarius68

Hydra

Helper
Staff member
Helper
Jan 26, 2019
23
6
3
30
mcmonkey said:
The timeout in the skin_url_task should probably be bumped to more like 30s. Mineskin's a bit slower now than it used to be.

Bumped it up - thanks for the notice
 
  • Like
Reactions: Andarius68