• 🏆 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!

Using SharpCraft in Battle.NET with only 1 player having it

Status
Not open for further replies.
I'm making a proper release package for people who want to add optional extensions to their map through SharpCraft.

Currently the optional feature is HTTP access. I created a demo which will automatically load your character from a web server, and save to it, as long as at least 1 player has SharpCraft in the map.

You can try the demo attached below.

Libraries:
  1. HTTP
  2. SharpCraft Extension (JASS)
  3. Sync

Here's a basic example on how to make SharpCraft an optional feature for your map. This works in Battle.Net and only requires 1 player to have SharpCraft running.

For demonstration I created a basic HTTP implementation. This lets you do simple GET/POST requests to websites.

With this you could do things like store and retrieve player data online, but there are much more possibilities.

JASS:
scope HttpExample initializer Init

    globals
        gamecache SharpCraftCache
    endglobals

    function HasSharpCraft takes nothing returns boolean
        return GetStoredBoolean(SharpCraftCache, "SC", "0")
    endfunction

    private function OnReceiveString takes nothing returns boolean
        local SyncData data = GetSyncedData()
 
        call BJDebugMsg(GetPlayerName(data.from) + " downloaded \"" + data.readString(0) + "\"")
 
        call data.destroy()
        return false
    endfunction
 
    private function OnMapStart takes nothing returns nothing
        local string response = ""
        local SyncData data
 
        call StoreString(SharpCraftCache, "ARG", "0", "HTTP")
        call StoreString(SharpCraftCache, "ARG", "1", "http://185.92.220.118/mmo/status.php")
        call StoreString(SharpCraftCache, "ARG", "2", "END")
 
        call Cheat("RunSharpCraftCommand")
 
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Downloading website...")
 
        call TriggerSleepAction(2.5)
 
        call Cheat("StoreLastResults HTTP")
 
        set response = GetStoredString(SharpCraftCache, "HTTP", "0")
 
        set data = SyncData.create(Player(0))
        call data.addString(response, 64)
        call data.addEventListener(function OnReceiveString)
        call data.start()
    endfunction

    //===========================================================================
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterTimerEvent(t, 0.00, false)
        call TriggerAddAction(t, function OnMapStart)
 
        set SharpCraftCache=InitGameCache("sc.w3v")
        call Cheat("SharpCraftInit")
        if (not HasSharpCraft()) then
            call InitGameCache("sc.w3v")
        endif
    endfunction

endscope
 

Attachments

  • HTTP Save System (SharpCraft).w3x
    116.3 KB · Views: 55
Last edited:

Deleted member 219079

D

Deleted member 219079

Where's the socket fiddling?
 

Deleted member 219079

D

Deleted member 219079

I don't know how to use VS it seems.. where can I get to the code?
7thJuly16.png
 
Here's the updated code:


C#:
using MindWorX.SharpCraft.Modules.JassAPI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using TinkerWorX.SharpCraft;
using System.Net;
using System.Threading;
using MindWorX.SharpCraft.Modules.WarAPI;

namespace TriggerHappy.Modules.HttpTrick
{
    [Requires(typeof(JassAPIPlugin))]
    unsafe public class HttpTrickPlugin : IMapPlugin
    {
        public static JassGameCache Cache;
        public static MindWorX.SharpCraft.Modules.WarAPI.Types.HT_Node* Global;

        public static Dictionary<int, string> LastResults = new Dictionary<int, string>();

        public HttpTrickPlugin()
        {
        }

        public void Initialize(PluginContext context)
        {
            if (context != PluginContext.Game)
                return;

            Natives.Add(new CheatPrototype(this.Cheat));
        }

        public void OnMapStart()
        {
            Natives.Add(new CheatPrototype(this.Cheat));

            //Cache = Natives.InitGameCache("sc.w3v");

            //Global = Game.Jass.AsUnsafe()->VirtualMachine[0]->GlobalTable->Lookup("SharpCraftCache");
            //Global->Value = Cache.Handle.ToPointer();
        }

        private delegate void CheatPrototype(JassStringArg cheatStr);
        private void Cheat(JassStringArg cheat)
        {
            try
            {
                string cmd;
                string cheatStr = cheat.ToString();
                bool hasArgs = false;

                if (cheatStr == "SharpCraftInit")
                {
                    Cache = Natives.InitGameCache("sc.w3v");

                    Natives.StoreBoolean(Cache, "SC", "0", true);

                    return;
                }

                if (cheatStr.Contains(' '))
                {
                    cmd = cheatStr.Split(' ')[0];
                    hasArgs = true;
                }
                else
                {
                    cmd = cheatStr;
                }

                switch (cmd)
                {
                    case "StoreLastResults":

                        if (!hasArgs)
                            return;

                        cmd = cheatStr.Split(' ')[1].ToUpper();

                        foreach (KeyValuePair<int, string> pair in LastResults)
                        {
                            Natives.StoreString(Cache, cmd, LastResults[pair.Key], pair.Value);
                        }

                        break;

                    case "RunSharpCraftCommand":

                        string sc_cmd = Natives.GetStoredString(Cache, "ARG", "0").ToString().ToLower();

                        string argStr = "";
                        string a;
                        int localPlayerId = Natives.GetPlayerId(JassPlayer.FromLocal());

                        for (int i = 1; i < 32; i++)
                        {
                            a = Natives.GetStoredString(Cache, "ARG", i.ToString());

                            if (a.ToLower() == "end")
                                break;

                            argStr += a + "\n";
                        }

                        string[] args = argStr.Split('\n');

                        switch (sc_cmd)
                        {
                            case "http":

                                if (args.Length == 0)
                                    return;

                                new Thread(delegate ()
                                {
                                    string response = "";
                                    int resultIndex = 0;
                                    string url = "";
                                    string method = "GET";
                                    string data = "";
                                    int player = -1;

                                    string[] split;
                                    string split_cmd;

                                    foreach (string arg in args)
                                    {
                                        if (arg.Contains('='))
                                        {
                                            split = arg.Split(new char[] { '=' }, 2);
                                            split_cmd = split[0].ToUpper().Trim().Replace(" ", "").Replace("\t", "");

                                            split[1] = split[1].Trim();

                                            if (split_cmd == "URL")
                                                url = split[1];
                                            else if (split_cmd == "ID")
                                                int.TryParse(split[1], out resultIndex);
                                            else if (split_cmd == "METHOD")
                                                method = split[1].Replace(" ", "");
                                            else if (split_cmd == "DATA")
                                                data = split[1].Replace(" ", "");
                                            else if (split_cmd == "PLAYER")
                                                int.TryParse(split[1], out player);
                                        }
                                    }

                                    if (player != -1 && localPlayerId != player)
                                        return;

                                    if (url == "")
                                        url = args[0];

                                    if (url.Substring(0, 7) != "http://")
                                        url = "http://" + url;

                                    try
                                    {
                                        if (method == "POST")
                                        {
                                            var wc = new WebClient();
                                            wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
                                            response = wc.UploadString(url, data);
                                            wc.Dispose();
                                        }
                                        else
                                        {
                                            response = new WebClient().DownloadString(url);
                                        }
                                    }
                                    catch(Exception error)
                                    {
                                        response = error.ToString();
                                    }

                                    LastResults[resultIndex] = response;

                                }).Start();

                                break;
                        }

                        break;


                    default:
                        Natives.Cheat(cheat);

                        break;


                }
            }
            catch(Exception error)
            {
                Natives.DisplayTextToPlayer(JassPlayer.FromLocal(), 0, 0, error.ToString());
            }
        }

        public void OnMapEnd()
        {
            //throw new NotImplementedException();
        }
    }
}
 
Last edited:

Deleted member 219079

D

Deleted member 219079

The code is sloppy but here.

I might make a proper release

C#:
using MindWorX.SharpCraft.Modules.JassAPI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using TinkerWorX.SharpCraft;
using System.Net;
using System.Threading;
using MindWorX.SharpCraft.Modules.WarAPI;

namespace TriggerHappy.Modules.HttpTrick
{
    [Requires(typeof(JassAPIPlugin))]
    unsafe public class HttpTrickPlugin : IMapPlugin
    {
        public static JassGameCache Cache;
        public static MindWorX.SharpCraft.Modules.WarAPI.Types.HT_Node* Global;
        public static string[] LastResults = new string[100];

        public HttpTrickPlugin()
        {
        }

        private void InitCache()
        {
            Cache = Natives.InitGameCache("sc.w3v");

            Natives.StoreBoolean(Cache, "SC", "0", true);
        }

        private void Cheat(JassStringArg cheatStr)
        {
            string cmd;

            if (cheatStr.ToString() == "SharpCraftInit")
            {
                InitCache();
                return;
            }
            else if(cheatStr.ToString().Contains("StoreLastResults") && cheatStr.ToString().Contains(" "))
            {
                cmd = cheatStr.ToString().Split(' ')[1];

                for (int i = 0; i < 100; i++)
                {
                    Natives.StoreString(Cache, cmd.ToUpper(), i.ToString(), LastResults[i]);
                }
            
            }
            else if (cheatStr.ToString() == "RunSharpCraftCommand")
            {
                cmd = Natives.GetStoredString(Cache, "ARG", "0").ToString().ToLower();
                string argStr = "";
                string a;

                for(int i=1; i<100; i++)
                {
                    a = Natives.GetStoredString(Cache, "ARG", i.ToString());

                    if (a.ToLower() == "end")
                        break;

                    argStr += a + "\n";
                }

                string[] args = argStr.Split('\n');

                switch (cmd)
                {
                    case "http":

                        new Thread(delegate ()
                        {
                            if (args.Length == 0)
                                return;

                            string response = "";

                            if (args.Length == 1)
                            {
                                response = new WebClient().DownloadString(args[0]);

                                LastResults[0] = response;
                            }
                            else
                            {
                                response = new WebClient().UploadString(args[0], args[1]);

                                LastResults[0] = response;
                            }
                        
                        }).Start();

                        break;
                    default:
                        Natives.Cheat(cheatStr);

                        break;
                }
            }
        }

        public void OnMapStart()
        {
            Natives.Add(new CheatPrototype(this.Cheat));

            /*Cache = Natives.InitGameCache("sc.w3v");

            Global = Game.Jass.AsUnsafe()->VirtualMachine[0]->GlobalTable->Lookup("udg_SharpCraftCache");
            Global->Value = (void*)Cache.Handle;*/

        
        }

        public void OnMapEnd()
        {

        }

        private delegate void CheatPrototype(JassStringArg cheatStr);

    }
}
Like java but doesn't suck not java! Ah C#.. I tried it once, I love the class - based approach but I had to remove VS back then cuz of lack of HD space.

Is there any concise tutorial you recommend when coming from C background?
 
Nice! Hashtables are pretty nice for this as well, since you can take a handle argument. But I like your implementation a lot. I could imagine someone making a preprocessor for this to make it really clean and fancy. :)

It opens up a lot of potential for user accounts and online interaction. I know a lot of people have invested tons of time into replay parsers and metadata for it, but with this it would be quite simple to just send that info through the game. No need for complex commands and junk, just a simple http post, and the fact that it is optional makes it even better.

And best of all, only one person needs it! I never thought about that fact. Thank goodness wc3 allows us to sync things. Unfortunately it is pretty slow through wc3 syncing, but it is cool nonetheless. I imagine you can have a bot with sharpcraft running and hosting a map, and then send/sync everything from it. :p Would be pretty cool!
 
Nice! Hashtables are pretty nice for this as well, since you can take a handle argument.

I couldn't get the code that accesses global variables to not crash so I used game cache

Thank goodness wc3 allows us to sync things. Unfortunately it is pretty slow through wc3 syncing, but it is cool nonetheless.

It's really not slow. It should have no problem loading a save code from a server and syncing it with all players. Even if only one player is syncing 11 separate, average-sized save codes it should still work relatively fast. Syncing entire web pages could be slow though and you can't do any kind of constant communication with the server quickly.

I imagine you can have a bot with sharpcraft running and hosting a map, and then send/sync everything from it. :p Would be pretty cool!

Yeah it could be good for RPGs or competitive maps.

It could also notify players of new updates, currently hosted maps, or even live stream messages to players (like implementing THW chat).

I will probably make a simple demo showing how to store save codes online and retrieve them.
 
Last edited:
It's really not slow. It should have no problem loading a save code from a server and syncing it with all players. Even if only one player is syncing 11 separate, average-sized save codes it should still work relatively fast. Syncing entire web pages could be slow though and you can't do any kind of constant communication with the server quickly.

Sorry, I didn't mean the http request is slow. I know that is fast since it doesn't have to go through bnet. I just meant wc3's syncing itself, but it shouldn't be a problem as long as you aren't trying to sync tons of things between players.

Anyway, good luck! :) That demo sounds like a good idea.
 
Sorry, I didn't mean the http request is slow. I know that is fast since it doesn't have to go through bnet. I just meant wc3's syncing itself, but it shouldn't be a problem as long as you aren't trying to sync tons of things between players.

Anyway, good luck! :) That demo sounds like a good idea.

I know what you meant, but I still mean the wc3 syncing is fast (enough).

If 1 player has SharpCraft, he's responsible to sync everything to all players, and it should be do-able in under a second with 11 average sized save codes.

I haven't tested Sync with more than 5 players in b.net though.
 
Last edited:
Level 6
Joined
Jul 30, 2013
Messages
282
wouldn't it be possible to accomplish http using preload natives + network file paths and maybe like a proxy server to turn smb into http and back.(speculation, interested but have not had time to tinker with it unfortunately)

talking about an on-line save/load repository you could even just have a plain old publicly acessible directory on a server. (obviously must be pedantic to not have epic security fails with this approach)

would then run without any custom war3 executables.

yeah having codeless save/load + unline repo for codes would be epic :D
 
I've decided to do something with this. I updated the first post with my current system code. I'm posting here until I finish everything.

Here's a proper example (included in demo map) of how to do an HTTP request with at least only 1 player having SharpCraft.

JASS:
scope HttpExample initializer Init
    
     private function OnDownloadString takes nothing returns boolean
        local HttpRequest req = GetEventHttpRequest() // or HttpRequest.last()
      
        call BJDebugMsg(GetPlayerName(req.player) + " downloaded \"" + req.response + "\"")
      
        call req.destroy()
      
        return false
    endfunction
    
    
    private function OnMapStart takes nothing returns nothing
        local HttpRequest http = HttpRequest.create(FindPlayerWithSharpCraft(), "www.hiveworkshop.com/attachments/hello-txt.248392/")

        set http.callback = Filter(function OnDownloadString)
      
        call BJDebugMsg("Downloading " + http.url)
      
        call http.start()
    endfunction

    //===========================================================================
    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 1, false, function OnMapStart)
    endfunction

endscope

So this would work if only a hostbot has sharpcraft installed?

I'm not sure if hostbots can run SharpCraft. I think it actually needs to run the game client. I could be wrong though.
 
Last edited:
Level 6
Joined
Jul 30, 2013
Messages
282
if somebody could make a version of ghost++ that had an api to make map_callable plugins in a not-terrible way.. that would be awesome.
and like http get / post would prolly be some of the most useful.. also a way to write/read server side data would be awfully nice for autoload-like systems if ppl change computers
 
if somebody could make a version of ghost++ that had an api to make map_callable plugins in a not-terrible way.. that would be awesome.
and like http get / post would prolly be some of the most useful.. also a way to write/read server side data would be awfully nice for autoload-like systems if ppl change computers

I'm already working on a autoload system where save codes are stored on my server. I will be releasing it soon.

and yeah ghost support would be nice
 
Level 6
Joined
Jul 30, 2013
Messages
282
kind of sad if it only works with a custo m war3.exe tho.
would it be possible to like abuse preload natives to do networking too? like with those windows nfs paths or sth?
 
kind of sad if it only works with a custo m war3.exe tho.
would it be possible to like abuse preload natives to do networking too? like with those windows nfs paths or sth?

I think the simplest solution would be a .mix file in the war3 directory.

But only 1 player needs to have it, so it's not too bad. It's also completely optional.
 

Deleted member 219079

D

Deleted member 219079

If you change contents of that attachment, will it be given different id? Will the client know the contents of that file? For example if there was in-game login system with passwords per user, and they'd be stored in that attachment, you wouldn't want the client him/herself to see the contents.
 
If you change contents of that attachment, will it be given different id? Will the client know the contents of that file? For example if there was in-game login system with passwords per user, and they'd be stored in that attachment, you wouldn't want the client him/herself to see the contents.

There's no way to differentiate maps yet, but I'm adding that soon.

Also why would the passwords be stored in a plugin? You would store them on a server (like I am doing now) which only you have access to. You should also never store passwords, but only the hash/salt. But, my demo doesn't use any passwords. It uses a "key" which you store your character under. So you type "-save someuniquekey" and "-load someuniquekey". Characters are automatically saved under the "auto" key, but you should also manually save in-case someone has your same name.

You could easily create a login system though.
 

Deleted member 219079

D

Deleted member 219079

I'm talking about "hello.txt" - attachment. Does its id in the url change if you update it's contents?

Edit: Relevant Computerphile video:
 

Deleted member 219079

D

Deleted member 219079

Relevant question :( You never answered this
Can you link to the solution you use? I'm interested
Like, you're not using any domain solution, it's just IP with some protection against denial of service attacks right?
 
Status
Not open for further replies.
Top