Skip to content

Instantly share code, notes, and snippets.

@Valkrysa
Created March 8, 2023 20:32
Show Gist options
  • Save Valkrysa/2d103cb910d2cac655e995013b6c969f to your computer and use it in GitHub Desktop.
Save Valkrysa/2d103cb910d2cac655e995013b6c969f to your computer and use it in GitHub Desktop.
https://www.hiveworkshop.com/pastebin/41be696537187bb3b209c20dafeb2a81.16058
JASS:
library W3HMC /*
******************************************************************
*
* v1.0.1 Beta, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* W3HMC allows maps to communicate with hostbots. Generally this code
* should be used by libraries which provide API for specific functionality
* like WebRequest.
* _________________________________________________________________________
* 1. Script Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy this script to your map and save it.
* _________________________________________________________________________
* 2. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* function IsHMCEnabled takes nothing returns boolean
* function OnHMCInit takes boolexpr func returns nothing
* function GetTriggerHMCRequest takes nothing returns HMCRequest
* function TriggerRegisterHostbotEvent takes trigger whichTrigger, integer eventId returns nothing
* _________________________________________________________________________
* 3. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* struct HMCRequest
*
* static method create takes integer reqType returns thistype // reqType should be one of the request type constants defined in this library. For example, W3HMC_REQUEST_HTTP.
*
* method destroy takes nothing returns nothing
* method addArg takes string arg, string value returns nothing // Add arguments to the request
* method sendFrom takes player anyPlayer returns boolean // Send the request to the hostbot from a player. Using GetLocalPlayer will force the hostbot to use the player with the lowest latency.
* method getResponse takes nothing returns string // Get the response from the hostbot.
*
* boolexpr onComplete // The function to be executed when a response is received.
*
* readonly integer reqType
* readonly boolean gotResponse
* readonly player sendingPlayer
* _________________________________________________________________________
* 4. Constants
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* constant integer EVENT_W3HMC_RESPONSE = 1
*
* constant integer W3HMC_REQUEST_INIT = 1
* constant integer W3HMC_REQUEST_HTTP = 2
* constant integer W3HMC_REQUEST_PLAYERREALM = 3
*
* constant integer W3HMC_ACTION_SET_ARGS = 1
* constant integer W3HMC_ACTION_EXECUTE = 2
* _________________________________________________________________________
* 5. Example
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* function CheckHMC takes nothing returns nothing
* local HMCRequest req = GetTriggerHMCRequest()
* set HMCEnabled = (req.getResponse() == "Ok.")
* call req.destroy()
* endfunction
*
* // check if the hostbot supports HMC
* local HMCRequest req = HMCRequest.create(W3HMC_REQUEST_INIT)
* set req.onComplete = Filter(function CheckHMC)
* call req.sendFrom(GetLocalPlayer()) // using GetLocalPlayer allows the hostbot to use the fastest player
* _________________________________________________________________________
* 6. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Hostbot generates a fake player.
* 2. Map communicates data to the hostbot through the gamecache sync natives.
* 3. Hostbot parses the gamecache action and checks for a valid HMC request.
* 4. Hostbot sends a response to the map through a chat message action from the fake player.
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/ requires AAAAAW3HMCLib
endlibrary
// JassHelper orders libraries alphabetically.
// To ensure this libraries globals are registered first, we prefix the library name with A's.
// If you don't like this, you will have to edit your hostbot config to use the proper
// trigger and player id by setting bot_w3hmcdebug = 1, playing as W3HMC_PLAYER and typing a chat message
// to receive the values.
library AAAAAW3HMCLib
globals
// Config
constant string W3HMC_CACHE_FILENAME = "W" // shorter names are faster on the network
constant player W3HMC_PLAYER = Player(4) // fake player generated by the hostbot
private constant real INIT_TIMEOUT = 5.00 // how long to wait before the system decides it's disabled
// Constants
constant integer EVENT_W3HMC_RESPONSE = 1
constant integer W3HMC_REQUEST_INIT = 1
constant integer W3HMC_REQUEST_HTTP = 2
constant integer W3HMC_REQUEST_PLAYERREALM = 3
constant integer W3HMC_REQUEST_DATETIME = 4
constant integer W3HMC_ACTION_SET_ARGS = 1
constant integer W3HMC_ACTION_EXECUTE = 2
// These need to be the first two handles created in your map
constant trigger W3HMC_TRIGGER = CreateTrigger()
constant event W3HMCEvent = TriggerRegisterPlayerChatEvent(W3HMC_TRIGGER, W3HMC_PLAYER, "", false)
// System vars
public boolean Initialized = false
private string array LastResponse
private real EventValue = -1
private HMCRequest LastRequest = 0
private trigger ExecTrig = CreateTrigger()
private HMCRequest InitRequest
private boolean Enabled = false
private trigger InitTrigger = CreateTrigger()
endglobals
private keyword ModuleInitializer
function IsHMCEnabled takes nothing returns boolean
return Enabled
endfunction
function OnHMCInit takes boolexpr func returns nothing
call TriggerAddCondition(InitTrigger, func)
endfunction
function GetTriggerHMCRequest takes nothing returns HMCRequest
return LastRequest
endfunction
function TriggerRegisterHostbotEvent takes trigger whichTrigger, integer eventId returns nothing
call TriggerRegisterVariableEvent(whichTrigger, SCOPE_PRIVATE + "EventValue", EQUAL, eventId)
endfunction
struct HMCRequest
readonly integer reqType
readonly boolean gotResponse
readonly player sendingPlayer
private string reqKey
private string args
private boolean running
boolexpr onComplete
readonly static gamecache Cache
static method create takes integer reqType returns thistype
local thistype this = thistype.allocate()
set this.reqType = reqType
set this.reqKey = I2S(reqType)
set this.gotResponse = false
set this.running = false
set this.args = ""
set this.onComplete = null
set LastResponse[this] = null
return this
endmethod
method destroy takes nothing returns nothing
set LastResponse[this] = null
set this.gotResponse = false
set this.running = false
set this.args = ""
call this.deallocate()
endmethod
method addArg takes string arg, string value returns nothing
set this.args = this.args + arg + " " + value + " "
endmethod
method sendFrom takes player anyPlayer returns boolean
local string gckey = I2S(this)
local string s = ""
if (LastResponse[this] != null or this.running) then
return false
endif
set this.running = true
set this.sendingPlayer = anyPlayer
if (this.args == null) then
set this.args = ""
elseif (StringLength(this.args) > 0) then
set this.args = SubString(this.args, 0, StringLength(this.args)-1) // trim last space
endif
if (StringLength(this.args) > 0) then
call StoreInteger(.Cache, gckey, this.args, W3HMC_ACTION_SET_ARGS)
endif
call StoreInteger(.Cache, gckey, this.reqKey, W3HMC_ACTION_EXECUTE)
if (GetLocalPlayer() == anyPlayer) then
if (StringLength(this.args) > 0) then
call SyncStoredInteger(.Cache, gckey, this.args)
endif
call SyncStoredInteger(.Cache, gckey, this.reqKey)
endif
return true
endmethod
method getResponse takes nothing returns string
return LastResponse[this]
endmethod
private static method OnChat takes nothing returns nothing
local string s = GetEventPlayerChatString()
local integer len = StringLength(s)
local integer i = 0
local string char
local string value = ""
local integer reqId = 0
local thistype req
// Parse the request ID from the response
loop
exitwhen i >= len
// Read the response one character at a time
set char = SubString(s, i, i + 1)
// We assume the request ID is stored before the first space
if (char == " ") then
if (reqId == 0) then
set reqId = S2I(value)
set value = ""
endif
else
set value = value + char
endif
set i = i + 1
endloop
set req = thistype(reqId)
// Trim the request ID from the response
set len = StringLength(I2S(reqId))
set LastResponse[reqId] = LastResponse[reqId] + SubString(s, len+1, StringLength(s))
// Finalize
set req.gotResponse = true
// Execute events
set LastRequest = thistype(reqId)
set EventValue = EVENT_W3HMC_RESPONSE
set EventValue = -1
if (req.onComplete != null) then
call TriggerClearConditions(ExecTrig)
call TriggerAddCondition(ExecTrig, req.onComplete)
call TriggerEvaluate(ExecTrig)
endif
endmethod
private static method checkTimeout takes nothing returns nothing
if (Initialized) then
return
endif
set Enabled = false
set Initialized = true
call DestroyTimer(GetExpiredTimer())
call TriggerEvaluate(InitTrigger)
endmethod
private static method checkCallback takes nothing returns nothing
local HMCRequest req = GetTriggerHMCRequest()
set Enabled = (req.getResponse() == "Ok.")
call req.destroy()
set Initialized = true
call TriggerEvaluate(InitTrigger)
endmethod
private static method onMapStart takes nothing returns nothing
set InitRequest = HMCRequest.create(W3HMC_REQUEST_INIT)
set InitRequest.onComplete = Filter(function thistype.checkCallback)
call InitRequest.sendFrom(GetLocalPlayer()) // using GetLocalPlayer allows the hostbot to use the fastest player
call TimerStart(GetExpiredTimer(), INIT_TIMEOUT, false, function thistype.checkTimeout)
endmethod
implement ModuleInitializer
endstruct
private module ModuleInitializer
private static method onInit takes nothing returns nothing
call FlushGameCache(InitGameCache(W3HMC_CACHE_FILENAME))
set .Cache = InitGameCache(W3HMC_CACHE_FILENAME)
call TriggerAddAction(W3HMC_TRIGGER, function HMCRequest.OnChat)
call TimerStart(CreateTimer(), 0.00, false, function thistype.onMapStart)
// prevent optimizers from removing or inlining the event
if (W3HMCEvent == null and W3HMCEvent == null) then
endif
endmethod
endmodule
endlibrary
Last edited: Jul 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment