Раздел:
Триггеры и объекты
Хотел я значит найти способ использовать фреймы мемхака в мультиплеере, и таки нашёл.
Сперва нашёл относительно старую наработку, но она была слишком неудобной.
Потом полез в заведомо рабочую китайскую доту с магазинами на фреймах и нашёл вот чего:
function D0G takes nothing returns nothing
	local timer t=GetExpiredTimer()
	local unit s=ATB(t,115)
	local integer i=ASB(t,105)
	local real x=0
	local real y=0
	local real x1=GetStoredReal(AL,NKB(s),"MouseX") //загрузить из хэша
	local real y1=GetStoredReal(AL,NKB(s),"MouseY")
	local real a=0
	if x1!=0 or y1!=0 then // если загрузилось
	call ZSA() //функция чистки
	set x=GetUnitX(s) 
	set y=GetUnitY(s)
	set a=Atan2(y1-y,x1-x) //найти угол
	call X3F(s,a,i) //начать шаманить
	endif
	set t=null
	set s=null
endfunction

function EAG takes unit s,integer i returns nothing
	local timer t=XXA() //взять таймер из кучи
	local real x=GetMouseX() //взять мышь мемхаком
	local real y=GetMouseY()
	if GetLocalPlayer()==GetOwningPlayer(s)then //взять конкретного игрока
	call StoreReal(AL,NKB(s),"MouseX",x) //засейвить мышь в хэш
	call StoreReal(AL,NKB(s),"MouseY",y)
	call SyncStoredReal(AL,NKB(s),"MouseX") //отправить кэш в синк
	call SyncStoredReal(AL,NKB(s),"MouseY")
	endif
	call StoreReal(AL,NKB(s),"MouseX",0) //перезаписать коорды всем
	call StoreReal(AL,NKB(s),"MouseY",0)
	call AUB(t,115,s) //передача данных вниз по потоку или чёто такое
	call ARB(t,105,i)
	call TimerStart(t,.005,true,function D0G)
	set t=null
endfunction
Как ни странно оно работало и десинков не вызывало.
Я написал свою версию этой фичи и выглядит она примерно вот так:
раскрыть
globals
    integer synced_int
    integer synced_int2
    real synced_time
    //if you will need this number for syncing, change it
    //this number indicates that "data-is-not-here-yet" and timer will wait for another number
    integer no_data_marker = -8192
endglobals

struct Sync
    static hashtable SyncHashTable = InitHashtable()
    static gamecache SyncCache = InitGameCache("Sync")
    static integer sync_offset = 8192
    boolexpr onfinish
    
    static method SyncInteger2Loop takes nothing returns nothing
        local thistype this = LoadInteger(SyncHashTable, GetHandleId(GetExpiredTimer()), 0)
        local integer loaded_int = GetStoredInteger(SyncCache, "0", I2S(this))
        local integer loaded_int2 = GetStoredInteger(SyncCache, "0", I2S(this+sync_offset))
        local boolean b = loaded_int != no_data_marker and loaded_int2 != no_data_marker
        local trigger t
        
        //call BJDebugMsg("SyncInteger2Loop: "+I2S(loaded_int)+", "+I2S(loaded_int2)+", boolexpr: "+I2S(GetHandleId(this.onfinish)))
        
        if(b) then
            set synced_int = loaded_int
            set synced_int2 = loaded_int2
            set synced_time = TimerGetElapsed(GetExpiredTimer())
            //execute block of code
            set t = CreateTrigger()
            call TriggerAddCondition(t, this.onfinish)
            call TriggerEvaluate(t)
            call DestroyTrigger(t)
            set t = null
            //
            call FlushStoredInteger(SyncCache, "0", I2S(this))
            call FlushChildHashtable(SyncHashTable, GetHandleId(GetExpiredTimer()))
            call DestroyTimer(GetExpiredTimer())
            //you cant run multiple boolexpr of same function at same time 
            //because Condition(function DoNothing) will return same boolexp
            //so save it outside and destroy later
            //call DestroyBoolExpr(this.onfinish)
            set this.onfinish = null
            call this.destroy()
        endif
        
    endmethod

    static method SyncMouseFrom takes player from_player, boolexpr onfinish returns nothing
        local thistype this = thistype.create()
        local timer t = CreateTimer()
        set this.onfinish = onfinish
        
        if(GetLocalPlayer()==from_player)then
            call StoreInteger(SyncCache, "0", I2S(this), R2I(GetMouseWorldX()))
            call StoreInteger(SyncCache, "0", I2S(this+sync_offset), R2I(GetMouseWorldY()))
            call SyncStoredInteger(SyncCache, "0", I2S(this))
            call SyncStoredInteger(SyncCache, "0", I2S(this+sync_offset))
        endif
        call StoreInteger(SyncCache, "0", I2S(this), no_data_marker)
        call StoreInteger(SyncCache, "0", I2S(this+sync_offset), no_data_marker)
        
        call SaveInteger(SyncHashTable, GetHandleId(t), 0, this)
        call TimerStart(t, 0.03125, true, function Sync.SyncInteger2Loop)
    endmethod
    
    static method SyncInt2From takes player from_player, integer a, integer b, boolexpr onfinish returns nothing
        local thistype this = thistype.create()
        local timer t = CreateTimer()
        set this.onfinish = onfinish
        
        if(a == no_data_marker or b == no_data_marker)then
            call BJDebugMsg("|cffff0000 Sync.SyncInt2From: trying to sync data that equals to no-data marker")
        endif
        
        if(GetLocalPlayer()==from_player)then
            call StoreInteger(SyncCache, "0", I2S(this), a)
            call StoreInteger(SyncCache, "0", I2S(this+sync_offset), b)
            call SyncStoredInteger(SyncCache, "0", I2S(this))
            call SyncStoredInteger(SyncCache, "0", I2S(this+sync_offset))
        endif
        call StoreInteger(SyncCache, "0", I2S(this), no_data_marker)
        call StoreInteger(SyncCache, "0", I2S(this+sync_offset), no_data_marker)
        
        call SaveInteger(SyncHashTable, GetHandleId(t), 0, this)
        call TimerStart(t, 0.03125, true, function Sync.SyncInteger2Loop)
    endmethod

    static method onInit takes nothing returns nothing
        set SyncCache = InitGameCache("Sync")
    endmethod
    
endstruct
Тесты:
раскрыть
globals
    unit mouse_move_unit
    timer sync_timer
    integer ints_to_sync = 700
    integer synced_amount = 0
    boolexpr sync_expr
endglobals

library SyncTest initializer Init_SyncTest
    
    function MegaSyncOnFinish takes nothing returns nothing
        call BJDebugMsg("Mega Sync finished")
        call BJDebugMsg("synced "+I2S(ints_to_sync)+" 8-digit ints for "+R2S(TimerGetElapsed(sync_timer)))
        call DestroyTimer(sync_timer)
        call DestroyBoolExpr(sync_expr)
        set sync_timer = null
    endfunction

    function MegaSyncOnSync takes nothing returns nothing
        call BJDebugMsg("+"+R2S(synced_time)+"sec:  received sync#"+I2S(synced_int2)+" with content "+I2S(synced_int))
        set synced_amount = synced_amount + 1
        if(synced_amount == ints_to_sync)then
            call MegaSyncOnFinish()
        endif
    endfunction
    
    function MegaSyncInit takes nothing returns nothing
        local integer i
        set sync_timer = CreateTimer()
        set sync_expr = Condition(function MegaSyncOnSync)
        
        call TimerStart(sync_timer, 9999, false, function DoNothing)
        
        call EnableOPLimit(false)
        set i = 0
        loop
            exitwhen i >= ints_to_sync
            call Sync.SyncInt2From(Player(0), GetRandomInt(10000000, 99999999), i, sync_expr)
            call BJDebugMsg(I2S(i))
            set i = i + 1
        endloop
    endfunction
    
    function MouseMoveTestLoop takes nothing returns nothing
        local integer mousex = synced_int
        local integer mousey = synced_int2
        
        call SetUnitX(mouse_move_unit, mousex)
        call SetUnitY(mouse_move_unit, mousey)
    endfunction
    
    function MouseMoveTestInit takes nothing returns nothing
        call Sync.SyncMouseFrom(Player(0), Condition(function MouseMoveTestLoop))
    endfunction
    
    function Init_SyncTest takes nothing returns nothing
        set mouse_move_unit = CreateUnit(Player(0), 'Hpal', 0., 0., 0.)
        call TimerStart(CreateTimer(), 0.03125, true, function MouseMoveTestInit)
        //call TimerStart(CreateTimer(), 1, false, function MegaSyncInit)
        call PanCameraTo(0., 0.)
    endfunction
    
endlibrary
Видосеки!
паладин перемещающийся по мыше красного игрока
синхронизация 700 интов
попытка синхронизации 2к интов
синхронизация 700 интов на пинге в 300
синхронизация паладина на пинге в 300
Теории о том как оно всё же работает и почему не больше 735
Работает оно потому что число из кэша сразу копируется в буфер синхронизации, и перезаписывая его мы ничего не теряем.
А функция синхронизации кэша гарантирует наличие этой информации у всех игроков (на это уходит несколько пакетов)
Мы же создаём периодический таймер который проверяет наличие информации в кэше и когда она появляется, закидывает информацию в глобалки и запускает булексп, а раз булексп запустился, значит глобалки должны быть на месте.
Среднее время синка - 0.16 секунд на ~500 8-значных интов, дальше оно какбы "дотекает" за следующие несколько секунд.
Почему лимит в 735 - возможно лимит ключей в кэше , что маловероятно, либо лимит одновременных таймеров что не запускают цикл проверки синка, либо внутренний лимит размера буфера синхронизации
Моя либа не является "финальной" и лишь показывает как это можно использовать
Надеюсь посмотрев функции вы поймёте как добавить ещё пару глобалок и функций на нужные вам типы данных
Пример сделан с учётом функций мемхака (планирую использовать его), однако можно использовать и без него.
Дада, используем DoNothing() :3
Карта-пример
Upd: Обновлённая либа
Upd2: Ещё более обновлённая либа
теперь детектит открытый чат - требуется MemoryHackWenHaoAPI плагин
и активное окно варкрафта - просто вызывается из под винды
раскрыть
library Sync
    
    globals
        integer pGetForegroundWindow = 0
    endglobals
    
    function IsWar3Active takes nothing returns boolean
        if(pGetForegroundWindow == 0)then
            set pGetForegroundWindow = GetModuleProcAddress( "User32.dll", "GetForegroundWindow" )
        endif
        
        return GetWarcraftWindow() == fast_call_1(pGetForegroundWindow, 0)
    endfunction
    
    //returns true if opened
    function IsChatOpened takes nothing returns boolean
        return WHGetChatState()
    endfunction
    
    globals
		integer g_syncmx
		integer g_syncmy
        player g_syncplayer
        integer g_syncpid
        gamecache gcSync = InitGameCache("Sync")
        hashtable htSync = InitHashtable()
		integer intSync_data = 2
    endglobals
	
	struct SyncInstance
		static integer offset = 8192
		
        player from_player
        timer keychecker
        timer synchecker
        integer keycode
        boolexpr onfinish
		
		//mousex + mousey
		static method SyncMouseCheck takes nothing returns nothing
			local thistype this = LoadInteger(htSync, GetHandleId(GetExpiredTimer()), 0)
			local boolean synced = HaveStoredInteger(gcSync, "0", I2S(this)) and HaveStoredInteger(gcSync, "0", I2S(this+offset))
			local trigger t
			
			if(synced)then
                set g_syncplayer = this.from_player
                set g_syncpid = GetPlayerId(this.from_player)
				set g_syncmx = GetStoredInteger(gcSync, "0", I2S(this))
				set g_syncmy = GetStoredInteger(gcSync, "0", I2S(this+offset))
				//
				set t = CreateTrigger()
				call TriggerAddCondition(t, this.onfinish)
				call TriggerEvaluate(t)
				call DestroyTrigger(t)
				set t = null
				call FlushStoredInteger(gcSync, "0", I2S(this))
                call FlushStoredInteger(gcSync, "0", I2S(this+offset))
				
				if(this.keychecker != null)then
					call PauseTimer(this.keychecker)
					call FlushChildHashtable(htSync, GetHandleId(this.keychecker))
					call DestroyTimer(this.keychecker)
					set this.keychecker = null
					set this.keycode = 0
				endif
				
				call PauseTimer(this.synchecker)
				call FlushChildHashtable(htSync, GetHandleId(this.synchecker))
				call DestroyTimer(this.synchecker)
				set this.onfinish = null
				set this.synchecker = null
				set this.from_player = null
				call this.destroy()
			endif
		endmethod
		
		//keypress flag
		static method SyncKeyCheck takes nothing returns nothing
			local thistype this = LoadInteger(htSync, GetHandleId(GetExpiredTimer()), 0)
			local boolean synced = HaveStoredInteger(gcSync, "0", I2S(this))
			local trigger t
			if(synced)then
				set g_syncplayer = this.from_player
				set t = CreateTrigger()
				call TriggerAddCondition(t, this.onfinish)
				call TriggerEvaluate(t)
				call DestroyTrigger(t)
				set t = null
				call PauseTimer(this.keychecker)
				call PauseTimer(this.synchecker)
				call FlushStoredInteger(gcSync, "0", I2S(this))
				call FlushChildHashtable(htSync, GetHandleId(this.keychecker))
				call FlushChildHashtable(htSync, GetHandleId(this.synchecker))
				call DestroyTimer(this.keychecker)
				call DestroyTimer(this.synchecker)
				set this.onfinish = null
				set this.keychecker = null
				set this.synchecker = null
				set this.from_player = null
				set this.keycode = 0
				call this.destroy()
			endif
		endmethod
		
		static method LocalKeyCheck takes nothing returns nothing
			local thistype this = LoadInteger(htSync, GetHandleId(GetExpiredTimer()), 0)
			if(GetLocalPlayer() == this.from_player)then
                if(IsChatOpened() == false and IsWar3Active())then
                    if(IsKeyPressed(this.keycode))then
                        call StoreInteger(gcSync, "0", I2S(this), intSync_data)
                        call SyncStoredInteger(gcSync, "0", I2S(this))
                        call FlushStoredInteger(gcSync, "0", I2S(this))
                        call PauseTimer(this.keychecker)
                    endif
				endif
			endif
		endmethod
		
		static method LocalKeyAndMouseCheck takes nothing returns nothing
			local thistype this = LoadInteger(htSync, GetHandleId(GetExpiredTimer()), 0)
			
			if(GetLocalPlayer() == this.from_player)then
                if(IsChatOpened() == false and IsWar3Active())then
                    if(IsKeyPressed(this.keycode))then
                        call StoreInteger(gcSync, "0", I2S(this), R2I(GetMouseWorldX()))
                        call StoreInteger(gcSync, "0", I2S(this+offset), R2I(GetMouseWorldY()))
                        call SyncStoredInteger(gcSync, "0", I2S(this))
                        call SyncStoredInteger(gcSync, "0", I2S(this+offset))
                        call FlushStoredInteger(gcSync, "0", I2S(this))
                        call FlushStoredInteger(gcSync, "0", I2S(this+offset))
                        
                        call PauseTimer(this.keychecker)
                    endif
                endif
			endif
		endmethod
    endstruct
    
    function SyncKeyFrom takes player from_player, integer keycode, boolexpr onfinish returns nothing
        local SyncInstance this = SyncInstance.create()
        set this.from_player = from_player
        set this.keychecker = CreateTimer()
        set this.synchecker = CreateTimer()
        set this.keycode = keycode
        set this.onfinish = onfinish
        
        call SaveInteger(htSync, GetHandleId(this.keychecker), 0, this)
        call SaveInteger(htSync, GetHandleId(this.synchecker), 0, this)
        call TimerStart(this.keychecker, 0.015, true, function SyncInstance.LocalKeyCheck)
        call TimerStart(this.synchecker, 0.015, true, function SyncInstance.SyncKeyCheck)
    endfunction

	function SyncMouseFrom takes player from_player, boolexpr onfinish returns nothing
        local SyncInstance this = SyncInstance.create()
        set this.synchecker = CreateTimer()
        set this.onfinish = onfinish
        set this.from_player = from_player
        
        if(GetLocalPlayer()==from_player)then
            call StoreInteger(gcSync, "0", I2S(this), R2I(GetMouseWorldX()))
            call StoreInteger(gcSync, "0", I2S(this+SyncInstance.offset), R2I(GetMouseWorldY()))
            call SyncStoredInteger(gcSync, "0", I2S(this))
            call SyncStoredInteger(gcSync, "0", I2S(this+SyncInstance.offset))
			call FlushStoredInteger(gcSync, "0", I2S(this))
			call FlushStoredInteger(gcSync, "0", I2S(this+SyncInstance.offset))
        endif
        
        call SaveInteger(htSync, GetHandleId(this.synchecker), 0, this)
        call TimerStart(this.synchecker, 0.03125, true, function SyncInstance.SyncMouseCheck)
    endfunction
	
	function SyncKeyAndMouseFrom takes player from_player, integer keycode, boolexpr onfinish returns nothing
        local SyncInstance this = SyncInstance.create()
        set this.from_player = from_player
        set this.keychecker = CreateTimer()
        set this.synchecker = CreateTimer()
        set this.keycode = keycode
        set this.onfinish = onfinish
		
        call SaveInteger(htSync, GetHandleId(this.keychecker), 0, this)
        call SaveInteger(htSync, GetHandleId(this.synchecker), 0, this)
        call TimerStart(this.keychecker, 0.015, true, function SyncInstance.LocalKeyAndMouseCheck)
        call TimerStart(this.synchecker, 0.015, true, function SyncInstance.SyncMouseCheck)
    endfunction
	
endlibrary
Описание
function SyncKeyFrom takes player from_player, integer keycode, boolexpr onfinish returns nothing
//вызывает переданный boolexpr после того как срабатывает синхронизация нажатия from_player кнопки keycode (https://docs.microsoft.com/ru-ru/windows/win32/inputdev/virtual-key-codes), игрока from_player можно достать в глобалке g_syncplayer
//переданный boolexpr уничтожайте сами

function SyncMouseFrom takes player from_player, boolexpr onfinish returns nothing
//вызывает переданный boolexpr после того как срабатывает синхронизация позиции мыши from_player
//игрока from_player можно достать в глобалке g_syncplayer, координаты мыши на местности - в g_syncmx и g_syncmy
//переданный boolexpr уничтожайте сами

function SyncKeyAndMouseFrom takes player from_player, integer keycode, boolexpr onfinish returns nothing
//вызывает переданный boolexpr после того как срабатывает синхронизация координат мыши игрока from_player 
// синхронизирует координаты мыши игрока from_player в момент нажатия keycode
//(https://docs.microsoft.com/ru-ru/windows/win32/inputdev/virtual-key-codes)
//игрока from_player можно достать в глобалке g_syncplayer, координаты мыши на местности - в g_syncmx и g_syncmy
//переданный boolexpr уничтожайте сами
Использовать можно например так:
library SyncTest initializer Init_SyncTest
    
    globals
        integer KEYCODE_Q = 0x51
        integer KEYCODE_W = 0x57
        integer KEYCODE_E = 0x45
        integer KEYCODE_R = 0x52
    endglobals
    
    function PlayerButton takes nothing returns nothing
        call BJDebugMsg("Player("+I2S(GetPlayerId(g_syncplayer))+") pressed his button!")
        call CreateUnit(g_syncplayer, 'Hpal', g_syncmx, g_syncmy, 0.)
    endfunction
    
    function SyncInit takes nothing returns nothing
        call SyncKeyAndMouseFrom(Player(0), KEYCODE_Q, Condition(function PlayerButton))
        call SyncKeyAndMouseFrom(Player(1), KEYCODE_W, Condition(function PlayerButton))
        call SyncKeyAndMouseFrom(Player(2), KEYCODE_E, Condition(function PlayerButton))
        call SyncKeyAndMouseFrom(Player(3), KEYCODE_R, Condition(function PlayerButton))
        
        call BJDebugMsg("SyncInit")
        call BJDebugMsg("press Q/W/E/R to spawn Paladin on cursor position!")
    endfunction
    
    function Init_SyncTest takes nothing returns nothing
        call TimerStart(CreateTimer(), 2, false, function SyncInit)
    endfunction
    
endlibrary
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
32
2 года назад
0
Всеравно кеш медленный, и это время очень зависит от игроков, бывали случаи когда синх занимал чуть ли не секунды.
2
17
2 года назад
Отредактирован N1ghtSiren
2
Времена идут, мнение насчёт кеша не меняется

синкайте через SelectUnit, всего то пару тыщ строк костылей написать
1
19
2 года назад
1
Классно сделано. Главное, что не через неполную синхронизацию.
0
17
2 года назад
0
Из минусов - надо придумать детект, свёрнут ли вар при отлове мыши / хоткеев
0
19
2 года назад
Отредактирован Ev3nt
0
N1ghtSiren, как идея, на плюсах можно проверять с помощью:
if (GetForegroundWindow() == FindWindow("Warcraft III", NULL)) {
    // Do smth
}
2
18
2 года назад
2
Нормас, лайк, как я понял - нужно оборачивать все данные в очередь, например - данные от разных игроков, если мы хотим использовать одни и те же ячейки в gamecache
2
15
2 года назад
2
Я так понимаю более быстрого способа сделать синк локальных действий на фреймах отсутствует? И потенциально такая система может приводить аж к секундным задержкам на малом объёме синхронизируемых данных?
0
14
11 месяцев назад
Отредактирован host_pi
0
код из GameCache2021.w3x
14000 строк controlc.com/943dd66c
Чтобы оставить комментарий, пожалуйста, войдите на сайт.