Ошибка: повторное срабатывание функции Javascript

  • Автор темы Автор темы Primo
  • Дата начала Дата начала
[quote author=Primo link=topic=1484.msg10200#msg10200 date=1498236728]
ZLOY, это логично, но разве PlayerResource:IsValidPlayer не должен проверять это? Или есть другой вариант проверки в игре ли игрок?
[/quote]
GetConnectionState
 
CryDeS, спасибо большое.

И еще одна очередная проблема :D Как ни странно
В общем, проблема такова, периодически обнуляются данные, которые записываются в hero.
Решил, что раз playerid обнуляется при отключении, а hero остаётся, то это хорошая возможность хранить индивидуальные данные, но этот подводный камень не даёт мне покоя.
Не могу понять, почему это происходит, какова причина.

Код, в котором используется таблица unitlist,а также другие переменные, например income. (нигде больше не используется)
Код:
GameRules.thisunitlist = {
	[1] = {name = "basic_unit_melee_1", count = 3},
	[2] = {name = "basic_unit_melee_2", count = 0},
	[3] = {name = "basic_unit_melee_3", count = 0},
	[4] = {name = "basic_unit_melee_4", count = 0},
	[5] = {name = "basic_unit_melee_5", count = 0},
	[6] = {name = "basic_unit_melee_6", count = 0},
	[7] = {name = "basic_unit_melee_7", count = 0},
	[8] = {name = "basic_unit_range_1", count = 1},
	[9] = {name = "basic_unit_range_2", count = 0},
	[10] = {name = "basic_unit_range_3", count = 0},
	[11] = {name = "basic_unit_range_4", count = 0},
	[12] = {name = "basic_unit_range_5", count = 0},
	[13] = {name = "basic_unit_range_6", count = 0},
	[14] = {name = "basic_unit_range_7", count = 0},
	[15] = {name = "basic_unit_special_1", count = 0},
	[16] = {name = "basic_unit_special_2", count = 0},
	[17] = {name = "basic_unit_special_3", count = 0},
	[18] = {name = "basic_unit_special_4", count = 0},
	[19] = {name = "basic_unit_special_5", count = 0},
	[20] = {name = "boss_unit_doom", count = 0},
}

function GameMode:OnHeroInGame(hero)
	hero.unitlist = deepcopy(GameRules.thisunitlist)
	hero.income = 1
end

function GameMode:OnBuyUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	if hero:GetGold() >= event.cost then
		hero.income = hero.income + event.income
		if event.count == 0 then
			local team = hero:GetTeam()
			spawnCreep(event.name, 1, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
		else
			for a, unit in pairs(hero.unitlist) do 
				if unit.name == event.name then
					unit.count = unit.count + event.count
					break
				end
			end
		end
		PlayerResource:SpendGold(event.PlayerID, event.cost, 1)
		DeepPrintTable(event)
	end
end

function GameMode:OnGameInProgress() -- Функция начнет выполняться, когда начнется матч( на часах будет 00:00 ).
	
	for playerID = 0, DOTA_MAX_TEAM_PLAYERS-1 do
		if PlayerResource:IsValidPlayerID(playerID) then
			local team = PlayerResource:GetTeam(playerID)
			keepers = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
			 
			-- Make the found units move to (0, 0, 0)
			for _,unit in pairs(keepers) do
				--local hero = PlayerResource:GetSelectedHeroEntity(playerID)
				--unit:SetOwner(hero)
				unit:SetControllableByPlayer(playerID, true)
			end
		end
	end
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			for key,unit in pairs(hero.unitlist) do
				if unit.count > 0 then
					spawnCreep(unit.name, unit.count, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
					Msg(hero:GetTeam() .. " - Spawned Creep - " .. unit.name .. " - Count: ".. unit.count .. "\n");				
				end
			end
		end
 		
		return ROUND_DELAY
	end)
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), INCOME_COUNT*hero.income, false, 1)
		end 		
		return INCOME_DELAY
	end)
end

Ошибка из консоли
Код:
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:172: attempt to perform arithmetic on field 'income' (a nil value)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function '__mul'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:172: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:168>
	[C]: in function 'xpcall'
	scripts\vscripts\libraries\timers.lua:136: in function <scripts\vscripts\libraries\timers.lua:94>
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:158: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function <scripts\vscripts\libraries\timers.lua:136>
	[C]: in function 'pairs'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:158: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:154>
	[C]: in function 'xpcall'
	scripts\vscripts\libraries\timers.lua:136: in function <scripts\vscripts\libraries\timers.lua:94>

P.S. игра идёт нормально, всё работает, но иногда случается так, что данные обнуляются, что портит игру. Когда и из-за чего это происходит понять не могу.
 
Последнее редактирование модератором:
Я может быть не в теме, да и вопрос твой толком не понял, но гляжу на шибки консоли и вижу:
во первых, ты предоставляешь код на 90 строк, а ошибки идут из строк 130+, да и не только с одного файла.
во вторых, у тебя точно существует income в event?
Код:
hero.income = hero.income + event.income
 
Илья, действительно, зря я не весь код предоставил.
Код:
-- This is the entry-point to your game mode and should be used primarily to precache models/particles/sounds/etc

require('internal/util')
require('gamemode')

keyvalue = {}
GameRules.unitdata = {}
GameRules.thisunitlist = {
	[1] = {name = "basic_unit_melee_1", count = 3},
	[2] = {name = "basic_unit_melee_2", count = 0},
	[3] = {name = "basic_unit_melee_3", count = 0},
	[4] = {name = "basic_unit_melee_4", count = 0},
	[5] = {name = "basic_unit_melee_5", count = 0},
	[6] = {name = "basic_unit_melee_6", count = 0},
	[7] = {name = "basic_unit_melee_7", count = 0},
	[8] = {name = "basic_unit_range_1", count = 1},
	[9] = {name = "basic_unit_range_2", count = 0},
	[10] = {name = "basic_unit_range_3", count = 0},
	[11] = {name = "basic_unit_range_4", count = 0},
	[12] = {name = "basic_unit_range_5", count = 0},
	[13] = {name = "basic_unit_range_6", count = 0},
	[14] = {name = "basic_unit_range_7", count = 0},
	[15] = {name = "basic_unit_special_1", count = 0},
	[16] = {name = "basic_unit_special_2", count = 0},
	[17] = {name = "basic_unit_special_3", count = 0},
	[18] = {name = "basic_unit_special_4", count = 0},
	[19] = {name = "basic_unit_special_5", count = 0},
	[20] = {name = "boss_unit_doom", count = 0},
}


function spawnCreep(unit, count, start_point, target_point, team)
	for i=1, count do
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )
	end	
end

function spawnOneCreep(unit, start_point, target_point, team)	
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )	
end

function GameMode:UnitPanelDebug(data)
	CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(data.PlayerID), "load_allunits", GameRules.unitdata )
	Msg("Unitpanel debug has called;")
end

function GameMode:OnAllPlayersLoaded()
	CustomGameEventManager:RegisterListener( "buy_unit", Dynamic_Wrap(GameMode, "OnBuyUnit") )
	CustomGameEventManager:RegisterListener( "unitpanel_debug", Dynamic_Wrap(GameMode, "UnitPanelDebug") )
	keyvalue = LoadKeyValues("scripts/npc/npc_units_custom.txt")
	Msg("Keyvalues loaded;")		
	for a, unit in pairs(GameRules.thisunitlist) do 
		GameRules.unitdata[unit.name] = {ancient = keyvalue[unit.name].AncientUnit, unitclass = keyvalue[unit.name].UnitClass, cost = keyvalue[unit.name].UnitCost, income = keyvalue[unit.name].UnitIncome, health = keyvalue[unit.name].StatusHealth, page = keyvalue[unit.name].UnitPage, mindamage = keyvalue[unit.name].AttackDamageMin, maxdamage = keyvalue[unit.name].AttackDamageMax, attackrange = keyvalue[unit.name].AttackRange, armor = keyvalue[unit.name].ArmorPhysical, ability1 = keyvalue[unit.name].Ability1, ability2 = keyvalue[unit.name].Ability2, ability3 = keyvalue[unit.name].Ability3, ability4 = keyvalue[unit.name].Ability4}
	end
	Msg("GameRules.unitdata loaded;")		
 
	Timers:CreateTimer(0.15, function()
		--CCustomGameEventManager::ScriptSend_ServerToPlayer - Invalid player
		for playerID = 0, DOTA_MAX_TEAM_PLAYERS-1 do
			if PlayerResource:GetConnectionState(playerID) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(playerID) then
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(playerID), "player_think", PlayerResource:GetSelectedHeroEntity(playerID) )
			end
		end
		return 0.15
	end)
	for team = 2, DOTA_TEAM_COUNT do
		if team ~= DOTA_TEAM_NEUTRALS and team ~= DOTA_TEAM_NOTEAM and team < 12 then
		
			local count = PlayerResource:GetPlayerCountForTeam(team)
			if count == 0 then
				local unitlist = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
				 
				-- Make the found units move to (0, 0, 0)
				for _,unit in pairs(unitlist) do
				  unit:ForceKill(false)
				end
			end
			
		end
	end
end
 
function GameMode:OnHeroInGame(hero)
	hero.unitlist = deepcopy(GameRules.thisunitlist)
	hero.income = 1
	CustomGameEventManager:Send_ServerToAllClients("load_allunits", GameRules.unitdata )	
	Msg("load_allunits event started;")
	--DeepPrintTable(GameRules.unitdata)
	--if PlayerResource:IsValidPlayer(hero:GetPlayerOwnerID()) then
	--	CustomGameEventManager:Send_ServerToPlayer( hero:GetPlayerOwner(), "load_allunits", GameRules.unitdata )
	--end
end

function deepcopy(object)
	local lookup_table = {}
	local function _copy(object)	
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		
		local new_table = {}	
		lookup_table[object] = new_table
		
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		
		return setmetatable(new_table, _copy(getmetatable(object)))
	end
	return _copy(object)
end

function GameMode:OnBuyUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	if hero:GetGold() >= event.cost then
		hero.income = hero.income + event.income
		if event.count == 0 then
			local team = hero:GetTeam()
			spawnCreep(event.name, 1, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
		else
			for a, unit in pairs(hero.unitlist) do 
				if unit.name == event.name then
					unit.count = unit.count + event.count
					break
				end
			end
		end
		PlayerResource:SpendGold(event.PlayerID, event.cost, 1)
		DeepPrintTable(event)
	end
	--DeepPrintTable(hero.unitlist)
	--GameRules:SendCustomMessage("<font color='#58ACFA'>Игрок купил юнит " .. event.name .. " !</font>", 0, 0)
end


function GameMode:OnGameInProgress() -- Функция начнет выполняться, когда начнется матч( на часах будет 00:00 ).
	
	for playerID = 0, DOTA_MAX_TEAM_PLAYERS-1 do
		if PlayerResource:IsValidPlayerID(playerID) then
			local team = PlayerResource:GetTeam(playerID)
			keepers = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
			 
			-- Make the found units move to (0, 0, 0)
			for _,unit in pairs(keepers) do
				--local hero = PlayerResource:GetSelectedHeroEntity(playerID)
				--unit:SetOwner(hero)
				unit:SetControllableByPlayer(playerID, true)
			end
		end
	end
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			for key,unit in pairs(hero.unitlist) do
				if unit.count > 0 then
					spawnCreep(unit.name, unit.count, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
					Msg(hero:GetTeam() .. " - Spawned Creep - " .. unit.name .. " - Count: ".. unit.count .. "\n");				
				end
			end
		end
 		
		return ROUND_DELAY
	end)
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), INCOME_COUNT*hero.income, false, 1)
		end 		
		return INCOME_DELAY
	end)
end

function Precache( context )

 -- Entire heroes (sound effects/voice/models/particles) can be precached with PrecacheUnitByNameSync
 -- Custom units from npc_units_custom.txt can also have all of their abilities and precache{} blocks precached in this way
 PrecacheUnitByNameSync("npc_dota_hero_dragon_knight", context)
 PrecacheUnitByNameSync("npc_dota_hero_sniper", context)
 PrecacheUnitByNameSync("npc_dota_hero_vengefulspirit", context)
 PrecacheUnitByNameSync("npc_dota_hero_treant", context)
 PrecacheUnitByNameSync("npc_dota_hero_omniknight", context)
 PrecacheUnitByNameSync("npc_dota_hero_doom_bringer", context)
end

-- Create the game mode when we activate
function Activate()
 GameRules.GameMode = GameMode()
 GameRules.GameMode:_InitGameMode()
end

event - данные, которые передаются непосредственно из javascript.
JS:
Код:
$.Msg("JAVASCRIPT SUCCESFULLY LOADED!");

var unitConf = {	
};

const maxPages = 4;
var currentPage = 1; 

function SelectPage(value)
{
	if(currentPage + value > 0 &amp;&amp; currentPage + value <= maxPages)
	{
		currentPage += value;
	}
	else if(currentPage + value == 0) currentPage = maxPages;
	else currentPage = 1;
	Game.EmitSound("ui.shortwhoosh");
	$('#PageLabelNumber').text = currentPage;
}

function OnPlayerThink( event_data )
{
	//$.Msg(unitConf['boss_unit_doom']);
	if(Object.keys(unitConf).length == 0)
	{
		GameEvents.SendCustomGameEventToServer( "unitpanel_debug", {thisdata : 1} );
		//$.Msg(unitConf);
	}
	$('#income-label').GetChild(1).text = event_data.income*10;
	for(var i in unitConf)
	{
		if(currentPage == unitConf[i].page) 
		{
			$(unitConf[i].unitclass).style.visibility = "visible";
		}
		else $(unitConf[i].unitclass).style.visibility = "collapse";
		
		if(Players.GetGold(Players.GetLocalPlayer()) < unitConf[i].cost)
		{
			$(unitConf[i].unitclass).style.border = "5px solid #9b0000";
		}
		else
		{
			$(unitConf[i].unitclass).style.border = "5px solid #199900";
			//Game.EmitSound("Quickbuy.Available");
		}
	}
	
	
	/*if(Players.GetGold(Players.GetLocalPlayer()) < 1000)
	{
		$("#unit_dragon_knight").style.backgroundColor = "red";
		$("#unit_sniper").style.backgroundColor = "red";
		//$.Msg("NOT ENOUGH MONEY!");
	}
	else 
	{
		$("#unit_dragon_knight").style.backgroundColor = "none";
		$("#unit_sniper").style.backgroundColor = "none";
	}*/
}



function OnAllUnitsLoad( event_data )
{
	unitConf = event_data;
	for(var i in unitConf)
	{		
		(function() {
			var m = i;
			var thisclass = $(unitConf[m].unitclass);
			thisclass.GetChild(1).text = unitConf[m].cost;
			var thislocal = $.Localize("#Game_tooltip_income")
					+unitConf[m].income*10
					+"<br>"+$.Localize("tooltip_"+m)+"<br>"
					+"<br>"+$.Localize("Game_tooltip_health")+"<font color='#ffee00'>"+unitConf[m].health+"</font>"
					+"<br>"+$.Localize("Game_tooltip_attackrange")+"<font color='#ffee00'>"+unitConf[m].attackrange+"</font>"
					+"<br>"+$.Localize("Game_tooltip_damage")+"<font color='#ffee00'>"+unitConf[m].mindamage+"-"+unitConf[m].maxdamage+"</font>"
					+"<br>"+$.Localize("Game_tooltip_armor")+"<font color='#ffee00'>"+unitConf[m].armor+"</font>";
					
			if(unitConf[m].ancient == 1) thislocal = $.Localize("#Game_tooltip_ancient") + thislocal;					
			if(unitConf[m].ability1 != "")
			{
				thislocal = thislocal+"<br>"+$.Localize("Game_tooltip_ability") +"<font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability1)+"</font>";
					if(unitConf[m].ability2 != "")
					{
						thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability2)+"</font>";
						if(unitConf[m].ability3 != "")
						{
							thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability3)+"</font>";
							if(unitConf[m].ability4 != "")
							{
								thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability4)+"</font>";
							}
						}
					}
			}				
					
			
			thisclass.SetPanelEvent("onmouseover", function(){ $.DispatchEvent("DOTAShowTextTooltip", thisclass, thislocal);});
			thisclass.SetPanelEvent("onmouseout", function(){ $.DispatchEvent("DOTAHideTextTooltip", thisclass);});				
		})(); 
	}
}
 
GameEvents.Subscribe( "player_think", OnPlayerThink);
GameEvents.Subscribe( "load_allunits", OnAllUnitsLoad);

function cmdBuyUnit(unitname, count)
{
	$.Msg( "EVENT: unit_buy - true ");
	if(Players.GetGold(Players.GetLocalPlayer()) >= unitConf[unitname].cost)
	{
		
		var data = {
			name: unitname,
			count: count,
			cost: unitConf[unitname].cost,
			income: unitConf[unitname].income
		};
		GameEvents.SendCustomGameEventToServer( "buy_unit", data );
		$.Msg( "EVENT: unit_buy_success - true ");
		Game.EmitSound("General.Buy");
	}
	else 
	{
		Game.EmitSound("General.NoGold");
	}
}
 
Последнее редактирование модератором:
Появилась мысль, что это из-за потерь. Предполагаю, что это может быть связано с
Код:
CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(playerID), "player_think", PlayerResource:GetSelectedHeroEntity(playerID) )
который срабатывает каждые 0.15сек
Убрал этот момент, посмотрим, что выйдет.
 
Последнее редактирование модератором:
В общем, проблема такова, периодически обнуляются данные, которые записываются в hero.
Решил, что раз playerid обнуляется при отключении, а hero остаётся, то это хорошая возможность хранить индивидуальные данные, но этот подводный камень не даёт мне покоя.
Не могу понять, почему это происходит, какова причина.
P.S. игра идёт нормально, всё работает, но иногда случается так, что данные обнуляются, что портит игру. Когда и из-за чего это происходит понять не могу.

В общем, проблема не решена. Нужна помощь, из-за этой проблемы вообще играть невозможно.
Актуальный код:
LUA
Код:
-- This is the entry-point to your game mode and should be used primarily to precache models/particles/sounds/etc

require('internal/util')
require('gamemode')

keyvalue = {}
GameRules.unitdata = {}
GameRules.thisunitlist = {
	{name = "basic_unit_melee_1", count = 3},
	{name = "basic_unit_melee_2", count = 0},
	{name = "basic_unit_melee_3", count = 0},
	{name = "basic_unit_melee_4", count = 0},
	{name = "basic_unit_melee_5", count = 0},
	{name = "basic_unit_melee_6", count = 0},
	{name = "basic_unit_melee_7", count = 0},
	{name = "basic_unit_range_1", count = 1},
	{name = "basic_unit_range_2", count = 0},
	{name = "basic_unit_range_3", count = 0},
	{name = "basic_unit_range_4", count = 0},
	{name = "basic_unit_range_5", count = 0},
	{name = "basic_unit_range_6", count = 0},
	{name = "basic_unit_range_7", count = 0},
	{name = "basic_unit_special_1", count = 0},
	{name = "basic_unit_special_2", count = 0},
	{name = "basic_unit_special_3", count = 0},
	{name = "basic_unit_special_4", count = 0},
	{name = "basic_unit_special_5", count = 0},
	{name = "boss_unit_doom", count = 0},
}


function spawnCreep(unit, count, start_point, target_point, team) 
	for i=1, count do
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )
	end	
end

function spawnOneCreep(unit, start_point, target_point, team)	
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )	
end

function GameMode:UnitPanelDebug(data)
	CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(data.PlayerID), "load_allunits", GameRules.unitdata )
	Msg("Unitpanel debug has called;")
end

function GameMode:OnAllPlayersLoaded()
	CustomGameEventManager:RegisterListener( "buy_unit", Dynamic_Wrap(GameMode, "OnBuyUnit") )
	CustomGameEventManager:RegisterListener( "sell_unit", Dynamic_Wrap(GameMode, "OnSellUnit") )
	CustomGameEventManager:RegisterListener( "unitpanel_debug", Dynamic_Wrap(GameMode, "UnitPanelDebug") )
	keyvalue = LoadKeyValues("scripts/npc/npc_units_custom.txt")
	Msg("Keyvalues loaded;")		
	for a, unit in pairs(GameRules.thisunitlist) do 
		GameRules.unitdata[unit.name] = {ancient = keyvalue[unit.name].AncientUnit, unitclass = keyvalue[unit.name].UnitClass, cost = keyvalue[unit.name].UnitCost, income = keyvalue[unit.name].UnitIncome, health = keyvalue[unit.name].StatusHealth, page = keyvalue[unit.name].UnitPage, mindamage = keyvalue[unit.name].AttackDamageMin, maxdamage = keyvalue[unit.name].AttackDamageMax, attackrange = keyvalue[unit.name].AttackRange, armor = keyvalue[unit.name].ArmorPhysical, ability1 = keyvalue[unit.name].Ability1, ability2 = keyvalue[unit.name].Ability2, ability3 = keyvalue[unit.name].Ability3, ability4 = keyvalue[unit.name].Ability4}
	end
	Msg("GameRules.unitdata loaded;")		
 

	for team = 2, DOTA_TEAM_COUNT do
		if team ~= DOTA_TEAM_NEUTRALS and team ~= DOTA_TEAM_NOTEAM and team < 12 then
		
			local count = PlayerResource:GetPlayerCountForTeam(team)
			if count == 0 then
				local unitlist = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
				 
				-- Make the found units move to (0, 0, 0)
				for _,unit in pairs(unitlist) do
				  unit:ForceKill(false)
				end
			end
			
		end
	end
end
 
function GameMode:OnHeroInGame(hero)
	hero.unitlist = deepcopy(GameRules.thisunitlist)
	hero.income = 1
	CustomGameEventManager:Send_ServerToAllClients("load_allunits", GameRules.unitdata )	
	Msg("load_allunits event started;")
	
	--Timers:CreateTimer(0.15, function()
		--CCustomGameEventManager::ScriptSend_ServerToPlayer - Invalid player
		--local owner = hero:GetPlayerOwner()
	--	local ownerid = hero:GetPlayerOwnerID()
	--		if PlayerResource:GetConnectionState(ownerid) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(ownerid) then
	--			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(ownerid), "player_think", {income = hero.income} )
	--		end

	--	return 0.15
	--end)
	
	local ownerid = hero:GetPlayerOwnerID()
	if PlayerResource:GetConnectionState(ownerid) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(ownerid) then
		CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(ownerid), "load_playerunits", hero.unitlist )
	end
	--DeepPrintTable(GameRules.unitdata)
	--if PlayerResource:IsValidPlayer(hero:GetPlayerOwnerID()) then
	--	CustomGameEventManager:Send_ServerToPlayer( hero:GetPlayerOwner(), "load_allunits", GameRules.unitdata )
	--end
end

function deepcopy(object)
	local lookup_table = {}
	local function _copy(object)	
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		
		local new_table = {}	
		lookup_table[object] = new_table
		
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		
		return setmetatable(new_table, _copy(getmetatable(object)))
	end
	return _copy(object)
end

function GameMode:OnBuyUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	if event.income ~= nil and event.count ~= nil and event.cost ~= nil then
		if hero:GetGold() >= event.cost then
			hero.income = hero.income + event.income
			if event.count == 0 then
				local team = hero:GetTeam()
				spawnCreep(event.name, 1, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
			else
				for a, unit in pairs(hero.unitlist) do 
					if unit.name == event.name then
						unit.count = unit.count + event.count
						break
					end
				end
			end
			PlayerResource:SpendGold(event.PlayerID, event.cost, 1)
			--DeepPrintTable(event)
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
			
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
		end
	end
	--DeepPrintTable(hero.unitlist)
	--GameRules:SendCustomMessage("<font color='#58ACFA'>Игрок купил юнит " .. event.name .. " !</font>", 0, 0)
end

function GameMode:OnSellUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	local unitcount = 0;
	local istrue = true
	if event.income ~= nil and event.count ~= nil then
		for a, unit in pairs(hero.unitlist) do
			if unit.name == event.name then
				if unit.count == 0 then
					istrue = false
					break
				end
			end
			if unit.count > 0 then unitcount = unitcount + unit.count end
		end
		if istrue == true then
			if unitcount > 4 then
				hero.income = hero.income - event.income
				PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), event.cost, false, 1)
				for a, unit in pairs(hero.unitlist) do
					if unit.name == event.name then
						unit.count = unit.count - event.count
						break
					end
				end
				--DeepPrintTable(event)
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
			end
		end
	end

end


function GameMode:OnGameInProgress() -- Функция начнет выполняться, когда начнется матч( на часах будет 00:00 ).
	
	for playerID = 0, DOTA_MAX_TEAM_PLAYERS-1 do
		if PlayerResource:IsValidPlayerID(playerID) then
			local team = PlayerResource:GetTeam(playerID)
			keepers = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
			 
			-- Make the found units move to (0, 0, 0)
			for _,unit in pairs(keepers) do
				--local hero = PlayerResource:GetSelectedHeroEntity(playerID)
				--unit:SetOwner(hero)
				unit:SetControllableByPlayer(playerID, true)
				unit:SetOwner(PlayerResource:GetPlayer(playerID))
			end
		end
	end
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			for key,unit in pairs(hero.unitlist) do
				if unit.count > 0 then
					spawnCreep(unit.name, unit.count, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
					Msg(hero:GetTeam() .. " - Spawned Creep - " .. unit.name .. " - Count: ".. unit.count .. "\n");				
				end
			end
		end
 		
		return ROUND_DELAY
	end)
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), INCOME_COUNT*hero.income, false, 1)
		end 		
		return INCOME_DELAY
	end)
end

function Precache( context )

 -- Entire heroes (sound effects/voice/models/particles) can be precached with PrecacheUnitByNameSync
 -- Custom units from npc_units_custom.txt can also have all of their abilities and precache{} blocks precached in this way
 PrecacheUnitByNameSync("npc_dota_hero_dragon_knight", context)
 PrecacheUnitByNameSync("npc_dota_hero_sniper", context)
 PrecacheUnitByNameSync("npc_dota_hero_vengefulspirit", context)
 PrecacheUnitByNameSync("npc_dota_hero_treant", context)
 PrecacheUnitByNameSync("npc_dota_hero_omniknight", context)
 PrecacheUnitByNameSync("npc_dota_hero_doom_bringer", context)
 PrecacheUnitByNameSync("npc_dota_Hero_crystal_maiden", context)
end

-- Create the game mode when we activate
function Activate()
 GameRules.GameMode = GameMode()
 GameRules.GameMode:_InitGameMode()
end

Javascript
Код:
$.Msg("JAVASCRIPT SUCCESFULLY LOADED!");

var unitConf = {};

var unitHaved = {};

const maxPages = 4;
var currentPage = 1; 
var myincome = 1;

//Runs a function periodically
//start - how much of an additional delay to have (on top of the tick refresh) (set this to -1 to run the function immediately
//time - how long to run for. set this to -1 to run infinitely
//tick - how many seconds to elapse between ticks (assumes time / tick is an integer) (set this to negative to run a set amount of ticks, e.g. -30 will run 30 ticks in whatever time period given)
//func - func to run (return true to cancel)
//UPDATE June 29 2015 - fixed some enclosure issues, added a safecheck for time overflow
$.Every = function(start, time, tick, func){
  var startTime = Game.Time();
  var tickRate = tick;
  if(tick < 1){
    if(start < 0) tick--;
    tickRate = time / -tick;
  }

  var tickCount = time/ tickRate;

  if(time < 0){
    tickCount = 9999999;
  }
  var numRan = 0;
  $.Schedule(start, (function(start,numRan,tickRate,tickCount){
    return function(){
      if(start < 0){ 
        start = 0;
        if(func()){
          return;
        }; 
      } 
      var tickNew = function(){
        numRan++;
        delay = (startTime+tickRate*numRan)-Game.Time();
        /* if((startTime+tickRate*numRan)-Game.Time() < 0){
          $.Msg('[ERROR] Function ' + func + ' taking too long to loop!')
          delay = 0;
        }*/
        $.Schedule(delay, function(){
          if(func()){
            return;
          };
          tickCount--;
          if(tickCount > 0) tickNew();
        });
      };
      tickNew();
    }
  })(start,numRan,tickRate,tickCount));
};

$.Every(0, -1, -10, function(){
  onJavaTimerTick();
});

function OnUnitBuySuccess(data)
{
	myincome = data.income;
	//$.Msg(data);
}
 
function onJavaTimerTick( )
{
	if(Object.keys(unitConf).length == 0)
	{
		GameEvents.SendCustomGameEventToServer( "unitpanel_debug", {thisdata : 1} );
		//$.Msg(unitConf);
	}
	$('#income-label').GetChild(1).text = myincome*10;
	for(var i in unitConf)
	{
		if(currentPage == unitConf[i].page) 
		{
			$(unitConf[i].unitclass).style.visibility = "visible";
		}
		else $(unitConf[i].unitclass).style.visibility = "collapse";
		
		if(Players.GetGold(Players.GetLocalPlayer()) < unitConf[i].cost)
		{ 
			$(unitConf[i].unitclass).style.border = "5px solid #9b0000";
		}
		else
		{
			$(unitConf[i].unitclass).style.border = "5px solid #199900";
			//Game.EmitSound("Quickbuy.Available");
		}
	}
}

function SelectPage(value)
{
	if(currentPage + value > 0 &amp;&amp; currentPage + value <= maxPages)
	{
		currentPage += value;
	}
	else if(currentPage + value == 0) currentPage = maxPages;
	else currentPage = 1;
	Game.EmitSound("ui.shortwhoosh");
	$('#PageLabelNumber').text = currentPage;
}




function OnAllUnitsLoad( event_data )
{
	unitConf = event_data;
	for(var i in unitConf)
	{		
		(function() {
			var m = i;
			var thisclass = $(unitConf[m].unitclass);
			thisclass.GetChild(1).text = unitConf[m].cost;
			var thislocal = $.Localize("#Game_tooltip_income")
					+unitConf[m].income*10
					+"<br>"+$.Localize("tooltip_"+m)+"<br>"
					+"<br>"+$.Localize("Game_tooltip_health")+"<font color='#ffee00'>"+unitConf[m].health+"</font>"
					+"<br>"+$.Localize("Game_tooltip_attackrange")+"<font color='#ffee00'>"+unitConf[m].attackrange+"</font>"
					+"<br>"+$.Localize("Game_tooltip_damage")+"<font color='#ffee00'>"+unitConf[m].mindamage+"-"+unitConf[m].maxdamage+"</font>"
					+"<br>"+$.Localize("Game_tooltip_armor")+"<font color='#ffee00'>"+unitConf[m].armor+"</font>";
					
			if(unitConf[m].ancient == 1) thislocal = $.Localize("#Game_tooltip_ancient") + thislocal;					
			if(unitConf[m].ability1 != "")
			{
				thislocal = thislocal+"<br>"+$.Localize("Game_tooltip_ability") +"<font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability1)+"</font>";
					if(unitConf[m].ability2 != "")
					{
						thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability2)+"</font>";
						if(unitConf[m].ability3 != "")
						{
							thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability3)+"</font>";
							if(unitConf[m].ability4 != "")
							{
								thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability4)+"</font>";
							}
						}
					}
			}				
					
			
			thisclass.SetPanelEvent("onmouseover", function(){ $.DispatchEvent("DOTAShowTextTooltip", thisclass, thislocal);});
			thisclass.SetPanelEvent("onmouseout", function(){ $.DispatchEvent("DOTAHideTextTooltip", thisclass);});				
		})(); 
	}
}

function OnPlayerUnitsLoad(data)
{
	//$.Msg(data);
	var newunits = {};
	for(var i in data)
	{
		(function() {
			var m = i;
			var name = data[m].name
			newunits[name] = {count: data[m].count, unitclass: "unitclass_"+name}
		})();
	}
	ReloadCurrentUnitsPanel(newunits);
}

function ReloadCurrentUnitsPanel(units)
{
	var unitlistlabel = $('#unitlist-label');
	unitlistlabel.RemoveAndDeleteChildren();	
	delete unitHaved;
	unitHaved = units;
	for(i in unitHaved)
	{
		(function() {
			var m = i;
			if(unitHaved[m].count > 0)
			{
				var thispanel = $.CreatePanel("Button", unitlistlabel, unitHaved[m].unitclass );
				var thislabel = $.CreatePanel("Label", thispanel, "label_"+unitHaved[m].unitclass);
				thispanel.SetPanelEvent( 'onactivate', function(){ cmdSellUnit(m, 1);});
				thispanel.AddClass("currentunit_list");
				thislabel.AddClass("currentunit_list_label");
				thispanel.style.border = "2px solid white"; 
				thislabel.text = "$";	
				thislabel.style.visibility = "collapse";
				thispanel.SetPanelEvent("onmouseover", function(){
					thislabel.style.visibility = "visible";
					thispanel.style.border = "3px solid yellow"; 
					$.DispatchEvent("DOTAShowTextTooltip", thispanel, $.Localize("#Game_tooltip_sellunit")+$.Localize("#tooltip_"+m)+"<br>"+ $.Localize("#Game_tooltip_currentunitcount") + unitHaved[m].count);
				});
				thispanel.SetPanelEvent("onmouseout", function(){
					thislabel.style.visibility = "collapse";					
					thispanel.style.border = "2px solid white"; 
					$.DispatchEvent("DOTAHideTextTooltip", thispanel);
				});		
				
				thispanel.style.backgroundSize = "100% 100%";
				thispanel.style.backgroundImage = "url('file://{images}/custom_game/avatar_"+m+".png')";
			}	
		})();
	}
}
 
function cmdSellUnit(unitname, count)
{
	if(unitHaved[unitname].count > 0)
	{
		$.Msg( "EVENT: unit_sell - true ");
		var unitcount = 0;
		for(var i in unitHaved)
		{
			(function(){
				var m = i;
				if(unitHaved[m].count > 0) unitcount += unitHaved[m].count;
				//$.Msg(unitcount);
			})();
		}
		if(unitcount > 4)
		{
			var data = {
				name: unitname,
				count: count,
				cost: unitConf[unitname].cost,
				income: unitConf[unitname].income
			};
			GameEvents.SendCustomGameEventToServer( "sell_unit", data );
			$.Msg( "EVENT: unit_sell_success - true ");
			Game.EmitSound("General.Sell");
		}
		else 
		{
			Game.EmitSound("General.NoGold");
		}		
	}

}
 
GameEvents.Subscribe( "unitbuy_success", OnUnitBuySuccess);
GameEvents.Subscribe( "load_allunits", OnAllUnitsLoad);
GameEvents.Subscribe( "load_playerunits", OnPlayerUnitsLoad);

function cmdBuyUnit(unitname, count)
{
	$.Msg( "EVENT: unit_buy - true ");
	if(Players.GetGold(Players.GetLocalPlayer()) >= unitConf[unitname].cost)
	{
		
		var data = {
			name: unitname,
			count: count,
			cost: unitConf[unitname].cost,
			income: unitConf[unitname].income
		};
		GameEvents.SendCustomGameEventToServer( "buy_unit", data );
		$.Msg( "EVENT: unit_buy_success - true ");
		Game.EmitSound("General.Buy");
	}
	else 
	{
		Game.EmitSound("General.NoGold");
	}
}

Ошибка:
Код:
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: attempt to perform arithmetic on field 'income' (a nil value)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function '__mul'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:219>
	[C]: in function 'xpcall'
	scripts\vscripts\libraries\timers.lua:136: in function <scripts\vscripts\libraries\timers.lua:94>

10 - Spawned Creep - basic_unit_melee_1 - Count: 3
10 - Spawned Creep - basic_unit_range_1 - Count: 2
6 - Spawned Creep - basic_unit_melee_1 - Count: 5
2 - Spawned Creep - basic_unit_melee_1 - Count: 4
2 - Spawned Creep - basic_unit_range_1 - Count: 1
8 - Spawned Creep - basic_unit_melee_1 - Count: 3
8 - Spawned Creep - basic_unit_range_1 - Count: 1
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:209: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function <scripts\vscripts\libraries\timers.lua:136>
	[C]: in function 'pairs'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:209: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:205>
	[C]: in function 'xpcall'
 
Последнее редактирование модератором:
[quote author=Primo link=topic=1484.msg10239#msg10239 date=1498478824]
В общем, проблема не решена. Нужна помощь, из-за этой проблемы вообще играть невозможно.
Актуальный код:
LUA
Код:
-- This is the entry-point to your game mode and should be used primarily to precache models/particles/sounds/etc

require('internal/util')
require('gamemode')

keyvalue = {}
GameRules.unitdata = {}
GameRules.thisunitlist = {
	{name = "basic_unit_melee_1", count = 3},
	{name = "basic_unit_melee_2", count = 0},
	{name = "basic_unit_melee_3", count = 0},
	{name = "basic_unit_melee_4", count = 0},
	{name = "basic_unit_melee_5", count = 0},
	{name = "basic_unit_melee_6", count = 0},
	{name = "basic_unit_melee_7", count = 0},
	{name = "basic_unit_range_1", count = 1},
	{name = "basic_unit_range_2", count = 0},
	{name = "basic_unit_range_3", count = 0},
	{name = "basic_unit_range_4", count = 0},
	{name = "basic_unit_range_5", count = 0},
	{name = "basic_unit_range_6", count = 0},
	{name = "basic_unit_range_7", count = 0},
	{name = "basic_unit_special_1", count = 0},
	{name = "basic_unit_special_2", count = 0},
	{name = "basic_unit_special_3", count = 0},
	{name = "basic_unit_special_4", count = 0},
	{name = "basic_unit_special_5", count = 0},
	{name = "boss_unit_doom", count = 0},
}


function spawnCreep(unit, count, start_point, target_point, team) 
	for i=1, count do
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )
	end	
end

function spawnOneCreep(unit, start_point, target_point, team)	
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )	
end

function GameMode:UnitPanelDebug(data)
	CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(data.PlayerID), "load_allunits", GameRules.unitdata )
	Msg("Unitpanel debug has called;")
end

function GameMode:OnAllPlayersLoaded()
	CustomGameEventManager:RegisterListener( "buy_unit", Dynamic_Wrap(GameMode, "OnBuyUnit") )
	CustomGameEventManager:RegisterListener( "sell_unit", Dynamic_Wrap(GameMode, "OnSellUnit") )
	CustomGameEventManager:RegisterListener( "unitpanel_debug", Dynamic_Wrap(GameMode, "UnitPanelDebug") )
	keyvalue = LoadKeyValues("scripts/npc/npc_units_custom.txt")
	Msg("Keyvalues loaded;")		
	for a, unit in pairs(GameRules.thisunitlist) do 
		GameRules.unitdata[unit.name] = {ancient = keyvalue[unit.name].AncientUnit, unitclass = keyvalue[unit.name].UnitClass, cost = keyvalue[unit.name].UnitCost, income = keyvalue[unit.name].UnitIncome, health = keyvalue[unit.name].StatusHealth, page = keyvalue[unit.name].UnitPage, mindamage = keyvalue[unit.name].AttackDamageMin, maxdamage = keyvalue[unit.name].AttackDamageMax, attackrange = keyvalue[unit.name].AttackRange, armor = keyvalue[unit.name].ArmorPhysical, ability1 = keyvalue[unit.name].Ability1, ability2 = keyvalue[unit.name].Ability2, ability3 = keyvalue[unit.name].Ability3, ability4 = keyvalue[unit.name].Ability4}
	end
	Msg("GameRules.unitdata loaded;")		
 

	for team = 2, DOTA_TEAM_COUNT do
		if team ~= DOTA_TEAM_NEUTRALS and team ~= DOTA_TEAM_NOTEAM and team < 12 then
		
			local count = PlayerResource:GetPlayerCountForTeam(team)
			if count == 0 then
				local unitlist = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
				 
				-- Make the found units move to (0, 0, 0)
				for _,unit in pairs(unitlist) do
				  unit:ForceKill(false)
				end
			end
			
		end
	end
end
 
function GameMode:OnHeroInGame(hero)
	hero.unitlist = deepcopy(GameRules.thisunitlist)
	hero.income = 1
	CustomGameEventManager:Send_ServerToAllClients("load_allunits", GameRules.unitdata )	
	Msg("load_allunits event started;")
	
	--Timers:CreateTimer(0.15, function()
		--CCustomGameEventManager::ScriptSend_ServerToPlayer - Invalid player
		--local owner = hero:GetPlayerOwner()
	--	local ownerid = hero:GetPlayerOwnerID()
	--		if PlayerResource:GetConnectionState(ownerid) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(ownerid) then
	--			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(ownerid), "player_think", {income = hero.income} )
	--		end

	--	return 0.15
	--end)
	
	local ownerid = hero:GetPlayerOwnerID()
	if PlayerResource:GetConnectionState(ownerid) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(ownerid) then
		CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(ownerid), "load_playerunits", hero.unitlist )
	end
	--DeepPrintTable(GameRules.unitdata)
	--if PlayerResource:IsValidPlayer(hero:GetPlayerOwnerID()) then
	--	CustomGameEventManager:Send_ServerToPlayer( hero:GetPlayerOwner(), "load_allunits", GameRules.unitdata )
	--end
end

function deepcopy(object)
	local lookup_table = {}
	local function _copy(object)	
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		
		local new_table = {}	
		lookup_table[object] = new_table
		
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		
		return setmetatable(new_table, _copy(getmetatable(object)))
	end
	return _copy(object)
end

function GameMode:OnBuyUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	if event.income ~= nil and event.count ~= nil and event.cost ~= nil then
		if hero:GetGold() >= event.cost then
			hero.income = hero.income + event.income
			if event.count == 0 then
				local team = hero:GetTeam()
				spawnCreep(event.name, 1, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
			else
				for a, unit in pairs(hero.unitlist) do 
					if unit.name == event.name then
						unit.count = unit.count + event.count
						break
					end
				end
			end
			PlayerResource:SpendGold(event.PlayerID, event.cost, 1)
			--DeepPrintTable(event)
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
			
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
		end
	end
	--DeepPrintTable(hero.unitlist)
	--GameRules:SendCustomMessage("<font color='#58ACFA'>Игрок купил юнит " .. event.name .. " !</font>", 0, 0)
end

function GameMode:OnSellUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	local unitcount = 0;
	local istrue = true
	if event.income ~= nil and event.count ~= nil then
		for a, unit in pairs(hero.unitlist) do
			if unit.name == event.name then
				if unit.count == 0 then
					istrue = false
					break
				end
			end
			if unit.count > 0 then unitcount = unitcount + unit.count end
		end
		if istrue == true then
			if unitcount > 4 then
				hero.income = hero.income - event.income
				PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), event.cost, false, 1)
				for a, unit in pairs(hero.unitlist) do
					if unit.name == event.name then
						unit.count = unit.count - event.count
						break
					end
				end
				--DeepPrintTable(event)
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
			end
		end
	end

end


function GameMode:OnGameInProgress() -- Функция начнет выполняться, когда начнется матч( на часах будет 00:00 ).
	
	for playerID = 0, DOTA_MAX_TEAM_PLAYERS-1 do
		if PlayerResource:IsValidPlayerID(playerID) then
			local team = PlayerResource:GetTeam(playerID)
			keepers = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
			 
			-- Make the found units move to (0, 0, 0)
			for _,unit in pairs(keepers) do
				--local hero = PlayerResource:GetSelectedHeroEntity(playerID)
				--unit:SetOwner(hero)
				unit:SetControllableByPlayer(playerID, true)
				unit:SetOwner(PlayerResource:GetPlayer(playerID))
			end
		end
	end
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			for key,unit in pairs(hero.unitlist) do
				if unit.count > 0 then
					spawnCreep(unit.name, unit.count, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
					Msg(hero:GetTeam() .. " - Spawned Creep - " .. unit.name .. " - Count: ".. unit.count .. "\n");				
				end
			end
		end
 		
		return ROUND_DELAY
	end)
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), INCOME_COUNT*hero.income, false, 1)
		end 		
		return INCOME_DELAY
	end)
end

function Precache( context )

 -- Entire heroes (sound effects/voice/models/particles) can be precached with PrecacheUnitByNameSync
 -- Custom units from npc_units_custom.txt can also have all of their abilities and precache{} blocks precached in this way
 PrecacheUnitByNameSync("npc_dota_hero_dragon_knight", context)
 PrecacheUnitByNameSync("npc_dota_hero_sniper", context)
 PrecacheUnitByNameSync("npc_dota_hero_vengefulspirit", context)
 PrecacheUnitByNameSync("npc_dota_hero_treant", context)
 PrecacheUnitByNameSync("npc_dota_hero_omniknight", context)
 PrecacheUnitByNameSync("npc_dota_hero_doom_bringer", context)
 PrecacheUnitByNameSync("npc_dota_Hero_crystal_maiden", context)
end

-- Create the game mode when we activate
function Activate()
 GameRules.GameMode = GameMode()
 GameRules.GameMode:_InitGameMode()
end

Javascript
Код:
$.Msg("JAVASCRIPT SUCCESFULLY LOADED!");

var unitConf = {};

var unitHaved = {};

const maxPages = 4;
var currentPage = 1; 
var myincome = 1;

//Runs a function periodically
//start - how much of an additional delay to have (on top of the tick refresh) (set this to -1 to run the function immediately
//time - how long to run for. set this to -1 to run infinitely
//tick - how many seconds to elapse between ticks (assumes time / tick is an integer) (set this to negative to run a set amount of ticks, e.g. -30 will run 30 ticks in whatever time period given)
//func - func to run (return true to cancel)
//UPDATE June 29 2015 - fixed some enclosure issues, added a safecheck for time overflow
$.Every = function(start, time, tick, func){
  var startTime = Game.Time();
  var tickRate = tick;
  if(tick < 1){
    if(start < 0) tick--;
    tickRate = time / -tick;
  }

  var tickCount = time/ tickRate;

  if(time < 0){
    tickCount = 9999999;
  }
  var numRan = 0;
  $.Schedule(start, (function(start,numRan,tickRate,tickCount){
    return function(){
      if(start < 0){ 
        start = 0;
        if(func()){
          return;
        }; 
      } 
      var tickNew = function(){
        numRan++;
        delay = (startTime+tickRate*numRan)-Game.Time();
        /* if((startTime+tickRate*numRan)-Game.Time() < 0){
          $.Msg('[ERROR] Function ' + func + ' taking too long to loop!')
          delay = 0;
        }*/
        $.Schedule(delay, function(){
          if(func()){
            return;
          };
          tickCount--;
          if(tickCount > 0) tickNew();
        });
      };
      tickNew();
    }
  })(start,numRan,tickRate,tickCount));
};

$.Every(0, -1, -10, function(){
  onJavaTimerTick();
});

function OnUnitBuySuccess(data)
{
	myincome = data.income;
	//$.Msg(data);
}
 
function onJavaTimerTick( )
{
	if(Object.keys(unitConf).length == 0)
	{
		GameEvents.SendCustomGameEventToServer( "unitpanel_debug", {thisdata : 1} );
		//$.Msg(unitConf);
	}
	$('#income-label').GetChild(1).text = myincome*10;
	for(var i in unitConf)
	{
		if(currentPage == unitConf[i].page) 
		{
			$(unitConf[i].unitclass).style.visibility = "visible";
		}
		else $(unitConf[i].unitclass).style.visibility = "collapse";
		
		if(Players.GetGold(Players.GetLocalPlayer()) < unitConf[i].cost)
		{ 
			$(unitConf[i].unitclass).style.border = "5px solid #9b0000";
		}
		else
		{
			$(unitConf[i].unitclass).style.border = "5px solid #199900";
			//Game.EmitSound("Quickbuy.Available");
		}
	}
}

function SelectPage(value)
{
	if(currentPage + value > 0 &amp;&amp; currentPage + value <= maxPages)
	{
		currentPage += value;
	}
	else if(currentPage + value == 0) currentPage = maxPages;
	else currentPage = 1;
	Game.EmitSound("ui.shortwhoosh");
	$('#PageLabelNumber').text = currentPage;
}




function OnAllUnitsLoad( event_data )
{
	unitConf = event_data;
	for(var i in unitConf)
	{		
		(function() {
			var m = i;
			var thisclass = $(unitConf[m].unitclass);
			thisclass.GetChild(1).text = unitConf[m].cost;
			var thislocal = $.Localize("#Game_tooltip_income")
					+unitConf[m].income*10
					+"<br>"+$.Localize("tooltip_"+m)+"<br>"
					+"<br>"+$.Localize("Game_tooltip_health")+"<font color='#ffee00'>"+unitConf[m].health+"</font>"
					+"<br>"+$.Localize("Game_tooltip_attackrange")+"<font color='#ffee00'>"+unitConf[m].attackrange+"</font>"
					+"<br>"+$.Localize("Game_tooltip_damage")+"<font color='#ffee00'>"+unitConf[m].mindamage+"-"+unitConf[m].maxdamage+"</font>"
					+"<br>"+$.Localize("Game_tooltip_armor")+"<font color='#ffee00'>"+unitConf[m].armor+"</font>";
					
			if(unitConf[m].ancient == 1) thislocal = $.Localize("#Game_tooltip_ancient") + thislocal;					
			if(unitConf[m].ability1 != "")
			{
				thislocal = thislocal+"<br>"+$.Localize("Game_tooltip_ability") +"<font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability1)+"</font>";
					if(unitConf[m].ability2 != "")
					{
						thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability2)+"</font>";
						if(unitConf[m].ability3 != "")
						{
							thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability3)+"</font>";
							if(unitConf[m].ability4 != "")
							{
								thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability4)+"</font>";
							}
						}
					}
			}				
					
			
			thisclass.SetPanelEvent("onmouseover", function(){ $.DispatchEvent("DOTAShowTextTooltip", thisclass, thislocal);});
			thisclass.SetPanelEvent("onmouseout", function(){ $.DispatchEvent("DOTAHideTextTooltip", thisclass);});				
		})(); 
	}
}

function OnPlayerUnitsLoad(data)
{
	//$.Msg(data);
	var newunits = {};
	for(var i in data)
	{
		(function() {
			var m = i;
			var name = data[m].name
			newunits[name] = {count: data[m].count, unitclass: "unitclass_"+name}
		})();
	}
	ReloadCurrentUnitsPanel(newunits);
}

function ReloadCurrentUnitsPanel(units)
{
	var unitlistlabel = $('#unitlist-label');
	unitlistlabel.RemoveAndDeleteChildren();	
	delete unitHaved;
	unitHaved = units;
	for(i in unitHaved)
	{
		(function() {
			var m = i;
			if(unitHaved[m].count > 0)
			{
				var thispanel = $.CreatePanel("Button", unitlistlabel, unitHaved[m].unitclass );
				var thislabel = $.CreatePanel("Label", thispanel, "label_"+unitHaved[m].unitclass);
				thispanel.SetPanelEvent( 'onactivate', function(){ cmdSellUnit(m, 1);});
				thispanel.AddClass("currentunit_list");
				thislabel.AddClass("currentunit_list_label");
				thispanel.style.border = "2px solid white"; 
				thislabel.text = "$";	
				thislabel.style.visibility = "collapse";
				thispanel.SetPanelEvent("onmouseover", function(){
					thislabel.style.visibility = "visible";
					thispanel.style.border = "3px solid yellow"; 
					$.DispatchEvent("DOTAShowTextTooltip", thispanel, $.Localize("#Game_tooltip_sellunit")+$.Localize("#tooltip_"+m)+"<br>"+ $.Localize("#Game_tooltip_currentunitcount") + unitHaved[m].count);
				});
				thispanel.SetPanelEvent("onmouseout", function(){
					thislabel.style.visibility = "collapse";					
					thispanel.style.border = "2px solid white"; 
					$.DispatchEvent("DOTAHideTextTooltip", thispanel);
				});		
				
				thispanel.style.backgroundSize = "100% 100%";
				thispanel.style.backgroundImage = "url('file://{images}/custom_game/avatar_"+m+".png')";
			}	
		})();
	}
}
 
function cmdSellUnit(unitname, count)
{
	if(unitHaved[unitname].count > 0)
	{
		$.Msg( "EVENT: unit_sell - true ");
		var unitcount = 0;
		for(var i in unitHaved)
		{
			(function(){
				var m = i;
				if(unitHaved[m].count > 0) unitcount += unitHaved[m].count;
				//$.Msg(unitcount);
			})();
		}
		if(unitcount > 4)
		{
			var data = {
				name: unitname,
				count: count,
				cost: unitConf[unitname].cost,
				income: unitConf[unitname].income
			};
			GameEvents.SendCustomGameEventToServer( "sell_unit", data );
			$.Msg( "EVENT: unit_sell_success - true ");
			Game.EmitSound("General.Sell");
		}
		else 
		{
			Game.EmitSound("General.NoGold");
		}		
	}

}
 
GameEvents.Subscribe( "unitbuy_success", OnUnitBuySuccess);
GameEvents.Subscribe( "load_allunits", OnAllUnitsLoad);
GameEvents.Subscribe( "load_playerunits", OnPlayerUnitsLoad);

function cmdBuyUnit(unitname, count)
{
	$.Msg( "EVENT: unit_buy - true ");
	if(Players.GetGold(Players.GetLocalPlayer()) >= unitConf[unitname].cost)
	{
		
		var data = {
			name: unitname,
			count: count,
			cost: unitConf[unitname].cost,
			income: unitConf[unitname].income
		};
		GameEvents.SendCustomGameEventToServer( "buy_unit", data );
		$.Msg( "EVENT: unit_buy_success - true ");
		Game.EmitSound("General.Buy");
	}
	else 
	{
		Game.EmitSound("General.NoGold");
	}
}

Ошибка:
Код:
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: attempt to perform arithmetic on field 'income' (a nil value)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function '__mul'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:219>
	[C]: in function 'xpcall'
	scripts\vscripts\libraries\timers.lua:136: in function <scripts\vscripts\libraries\timers.lua:94>

10 - Spawned Creep - basic_unit_melee_1 - Count: 3
10 - Spawned Creep - basic_unit_range_1 - Count: 2
6 - Spawned Creep - basic_unit_melee_1 - Count: 5
2 - Spawned Creep - basic_unit_melee_1 - Count: 4
2 - Spawned Creep - basic_unit_range_1 - Count: 1
8 - Spawned Creep - basic_unit_melee_1 - Count: 3
8 - Spawned Creep - basic_unit_range_1 - Count: 1
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:209: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function <scripts\vscripts\libraries\timers.lua:136>
	[C]: in function 'pairs'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:209: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:205>
	[C]: in function 'xpcall'
[/quote]
мб неттейблы заюзаешь для хранения данных?
 
Последнее редактирование модератором:
У тебя вот ключевая ошибка:

Код:
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: attempt to perform arithmetic on field 'income' (a nil value)

Где-то в процессе работы нету income. Я не в теме и не въезжаю, что ты делаешь, но если не можешь придумать, как её исправить или обойти - переделай архитектуру, саму логику своей механики.
 
CryDeS, можно пример?
Илья, суть проста, в js вызывается событие GameEvents.SendCustomGameEventToServer( "buy_unit", data );, в котором передаются такие данные:
Код:
			name: unitname,
			count: count,
			cost: unitConf[unitname].cost,
			income: unitConf[unitname].income
в LUA это ловится
Код:
CustomGameEventManager:RegisterListener( "buy_unit", Dynamic_Wrap(GameMode, "OnBuyUnit") )
function GameMode:OnBuyUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	if event.income ~= nil and event.count ~= nil and event.cost ~= nil then
		if hero:GetGold() >= event.cost then
			hero.income = hero.income + event.income
			if event.count == 0 then
				local team = hero:GetTeam()
				spawnCreep(event.name, 1, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
			else
				for a, unit in pairs(hero.unitlist) do 
					if unit.name == event.name then
						unit.count = unit.count + event.count
						break
					end
				end
			end
			PlayerResource:SpendGold(event.PlayerID, event.cost, 1)
			--DeepPrintTable(event)
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
			
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
		end
	end
	--DeepPrintTable(hero.unitlist)
	--GameRules:SendCustomMessage("<font color='#58ACFA'>Игрок купил юнит " .. event.name .. " !</font>", 0, 0)
end
Суть в том, что income в JS записан в переменную и никогда не меняется. Как он может быть пустым, не понимаю.
 
Последнее редактирование модератором:
Вы че прикалываетесь? Какого хрена вообще на сервер из джаваскрипта передается инкам и стоимость? А потом как крайдес будете плакать что в кастомке читерят?
 
MahouShoujo, ну момент щепетильный, и всегда поправимый, но хотелось бы услышать мнение о сложившейся проблеме.
 
Ну так что исправлять если ошибка в переменной income которой вообще там не должно быть?
 
Проблема не решена
Код:
[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:209: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function <scripts\vscripts\libraries\timers.lua:136>
	[C]: in function 'pairs'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:209: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:205>
	[C]: in function 'xpcall'
	scripts\vscripts\libraries\timers.lua:136: in function <scripts\vscripts\libraries\timers.lua:94>

[VScript] ...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: attempt to perform arithmetic on field 'income' (a nil value)
stack traceback:
	scripts\vscripts\libraries\timers.lua:137: in function '__mul'
	...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:223: in function <...94786\943694786.vpk:scripts\vscripts\addon_game_mode.lua:219>
	[C]: in function 'xpcall'
	scripts\vscripts\libraries\timers.lua:136: in function <scripts\vscripts\libraries\timers.lua:94>
Актуальный код:
LUA
Код:
-- This is the entry-point to your game mode and should be used primarily to precache models/particles/sounds/etc

require('internal/util')
require('gamemode')

keyvalue = {}
GameRules.unitdata = {}
GameRules.thisunitlist = {
	{name = "basic_unit_melee_1", count = 3},
	{name = "basic_unit_melee_2", count = 0},
	{name = "basic_unit_melee_3", count = 0},
	{name = "basic_unit_melee_4", count = 0},
	{name = "basic_unit_melee_5", count = 0},
	{name = "basic_unit_melee_6", count = 0},
	{name = "basic_unit_melee_7", count = 0},
	{name = "basic_unit_range_1", count = 1},
	{name = "basic_unit_range_2", count = 0},
	{name = "basic_unit_range_3", count = 0},
	{name = "basic_unit_range_4", count = 0},
	{name = "basic_unit_range_5", count = 0},
	{name = "basic_unit_range_6", count = 0},
	{name = "basic_unit_range_7", count = 0},
	{name = "basic_unit_special_1", count = 0},
	{name = "basic_unit_special_2", count = 0},
	{name = "basic_unit_special_3", count = 0},
	{name = "basic_unit_special_4", count = 0},
	{name = "basic_unit_special_5", count = 0},
	{name = "boss_unit_doom", count = 0},
}


function spawnCreep(unit, count, start_point, target_point, team) 
	for i=1, count do
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )
	end	
end

function spawnOneCreep(unit, start_point, target_point, team)	
		local r_unit = CreateUnitByName( unit, start_point + RandomVector( RandomFloat( 0, 200 ) ), true, nil, nil, team )
		r_unit:SetInitialGoalEntity( target_point )	
end

function GameMode:UnitPanelDebug(data)
	CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(data.PlayerID), "load_allunits", GameRules.unitdata )
	Msg("Unitpanel debug has called;")
end

function GameMode:OnAllPlayersLoaded()
	CustomGameEventManager:RegisterListener( "buy_unit", Dynamic_Wrap(GameMode, "OnBuyUnit") )
	CustomGameEventManager:RegisterListener( "sell_unit", Dynamic_Wrap(GameMode, "OnSellUnit") )
	CustomGameEventManager:RegisterListener( "unitpanel_debug", Dynamic_Wrap(GameMode, "UnitPanelDebug") )
	keyvalue = LoadKeyValues("scripts/npc/npc_units_custom.txt")
	Msg("Keyvalues loaded;")		
	for a, unit in pairs(GameRules.thisunitlist) do 
		GameRules.unitdata[unit.name] = {ancient = keyvalue[unit.name].AncientUnit, unitclass = keyvalue[unit.name].UnitClass, cost = keyvalue[unit.name].UnitCost, income = keyvalue[unit.name].UnitIncome, health = keyvalue[unit.name].StatusHealth, page = keyvalue[unit.name].UnitPage, mindamage = keyvalue[unit.name].AttackDamageMin, maxdamage = keyvalue[unit.name].AttackDamageMax, attackrange = keyvalue[unit.name].AttackRange, armor = keyvalue[unit.name].ArmorPhysical, ability1 = keyvalue[unit.name].Ability1, ability2 = keyvalue[unit.name].Ability2, ability3 = keyvalue[unit.name].Ability3, ability4 = keyvalue[unit.name].Ability4}
	end
	Msg("GameRules.unitdata loaded;")		
 

	for team = 2, DOTA_TEAM_COUNT do
		if team ~= DOTA_TEAM_NEUTRALS and team ~= DOTA_TEAM_NOTEAM and team < 12 then
		
			local count = PlayerResource:GetPlayerCountForTeam(team)
			if count == 0 then
				local unitlist = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
				 
				-- Make the found units move to (0, 0, 0)
				for _,unit in pairs(unitlist) do
				  unit:ForceKill(false)
				end
			end
			
		end
	end
end
 
function GameMode:OnHeroInGame(hero)
	hero.unitlist = deepcopy(GameRules.thisunitlist)
	hero.income = 1
	CustomGameEventManager:Send_ServerToAllClients("load_allunits", GameRules.unitdata )	
	Msg("load_allunits event started;")
	
	--Timers:CreateTimer(0.15, function()
		--CCustomGameEventManager::ScriptSend_ServerToPlayer - Invalid player
		--local owner = hero:GetPlayerOwner()
	--	local ownerid = hero:GetPlayerOwnerID()
	--		if PlayerResource:GetConnectionState(ownerid) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(ownerid) then
	--			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(ownerid), "player_think", {income = hero.income} )
	--		end

	--	return 0.15
	--end)
	
	local ownerid = hero:GetPlayerOwnerID()
	if PlayerResource:GetConnectionState(ownerid) == DOTA_CONNECTION_STATE_CONNECTED and PlayerResource:IsValidPlayer(ownerid) then
		CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(ownerid), "load_playerunits", hero.unitlist )
	end
	--DeepPrintTable(GameRules.unitdata)
	--if PlayerResource:IsValidPlayer(hero:GetPlayerOwnerID()) then
	--	CustomGameEventManager:Send_ServerToPlayer( hero:GetPlayerOwner(), "load_allunits", GameRules.unitdata )
	--end
end

function deepcopy(object)
	local lookup_table = {}
	local function _copy(object)	
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		
		local new_table = {}	
		lookup_table[object] = new_table
		
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		
		return setmetatable(new_table, _copy(getmetatable(object)))
	end
	return _copy(object)
end

function GameMode:OnBuyUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	if event.count ~= nil then
		if hero:GetGold() >= GameRules.unitdata[event.name].cost then
			hero.income = hero.income + GameRules.unitdata[event.name].income
			if event.count == 0 then
				local team = hero:GetTeam()
				spawnCreep(event.name, 1, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
			else
				for a, unit in pairs(hero.unitlist) do 
					if unit.name == event.name then
						unit.count = unit.count + event.count
						break
					end
				end
			end
			PlayerResource:SpendGold(event.PlayerID, GameRules.unitdata[event.name].cost, 1)
			--DeepPrintTable(event)
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
			
			CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
		end
	end
	--DeepPrintTable(hero.unitlist)
	--GameRules:SendCustomMessage("<font color='#58ACFA'>Игрок купил юнит " .. event.name .. " !</font>", 0, 0)
end

function GameMode:OnSellUnit(event)
	
	local hero = PlayerResource:GetSelectedHeroEntity(event.PlayerID)
	local unitcount = 0;
	local istrue = true
	if event.count ~= nil then
		for a, unit in pairs(hero.unitlist) do
			if unit.name == event.name then
				if unit.count == 0 then
					istrue = false
					break
				end
			end
			if unit.count > 0 then unitcount = unitcount + unit.count end
		end
		if istrue == true then
			if unitcount > 4 then
				hero.income = hero.income - GameRules.unitdata[event.name].income
				PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), GameRules.unitdata[event.name].cost, false, 1)
				for a, unit in pairs(hero.unitlist) do
					if unit.name == event.name then
						unit.count = unit.count - event.count
						break
					end
				end
				--DeepPrintTable(event)
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "load_playerunits", hero.unitlist )
				CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(event.PlayerID), "unitbuy_success", {income = hero.income} )
			end
		end
	end

end


function GameMode:OnGameInProgress() -- Функция начнет выполняться, когда начнется матч( на часах будет 00:00 ).
	
	for playerID = 0, DOTA_MAX_TEAM_PLAYERS-1 do
		if PlayerResource:IsValidPlayerID(playerID) then
			local team = PlayerResource:GetTeam(playerID)
			keepers = FindUnitsInRadius(team, Vector(0, 0, 0), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false)
			 
			-- Make the found units move to (0, 0, 0)
			for _,unit in pairs(keepers) do
				--local hero = PlayerResource:GetSelectedHeroEntity(playerID)
				--unit:SetOwner(hero)
				unit:SetControllableByPlayer(playerID, true)
				unit:SetOwner(PlayerResource:GetPlayer(playerID))
			end
		end
	end
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			for key,unit in pairs(hero.unitlist) do
				if unit.count > 0 then
					spawnCreep(unit.name, unit.count, Entities:FindByName( nil, "spawn_" .. team):GetAbsOrigin(), Entities:FindByName( nil, "point_" .. team), team)
					Msg(hero:GetTeam() .. " - Spawned Creep - " .. unit.name .. " - Count: ".. unit.count .. "\n");				
				end
			end
		end
 		
		return ROUND_DELAY
	end)
	Timers:CreateTimer(0.1, function()
		local allHeroes = HeroList:GetAllHeroes()
		for _,hero in pairs(allHeroes) do
			local team = hero:GetTeam()
			PlayerResource:ModifyGold(hero:GetPlayerOwnerID(), INCOME_COUNT*hero.income, false, 1)
		end 		
		return INCOME_DELAY
	end)
end

function Precache( context )

 -- Entire heroes (sound effects/voice/models/particles) can be precached with PrecacheUnitByNameSync
 -- Custom units from npc_units_custom.txt can also have all of their abilities and precache{} blocks precached in this way
 PrecacheUnitByNameSync("npc_dota_hero_dragon_knight", context)
 PrecacheUnitByNameSync("npc_dota_hero_sniper", context)
 PrecacheUnitByNameSync("npc_dota_hero_vengefulspirit", context)
 PrecacheUnitByNameSync("npc_dota_hero_treant", context)
 PrecacheUnitByNameSync("npc_dota_hero_omniknight", context)
 PrecacheUnitByNameSync("npc_dota_hero_doom_bringer", context)
 PrecacheUnitByNameSync("npc_dota_Hero_crystal_maiden", context)
end

-- Create the game mode when we activate
function Activate()
 GameRules.GameMode = GameMode()
 GameRules.GameMode:_InitGameMode()
end

JAVASCRIPT
Код:
$.Msg("JAVASCRIPT SUCCESFULLY LOADED!");

var unitConf = {};

var unitHaved = {};

const maxPages = 4;
var currentPage = 1; 
var myincome = 1;

//Runs a function periodically
//start - how much of an additional delay to have (on top of the tick refresh) (set this to -1 to run the function immediately
//time - how long to run for. set this to -1 to run infinitely
//tick - how many seconds to elapse between ticks (assumes time / tick is an integer) (set this to negative to run a set amount of ticks, e.g. -30 will run 30 ticks in whatever time period given)
//func - func to run (return true to cancel)
//UPDATE June 29 2015 - fixed some enclosure issues, added a safecheck for time overflow
$.Every = function(start, time, tick, func){
  var startTime = Game.Time();
  var tickRate = tick;
  if(tick < 1){
    if(start < 0) tick--;
    tickRate = time / -tick;
  }

  var tickCount = time/ tickRate;

  if(time < 0){
    tickCount = 9999999;
  }
  var numRan = 0;
  $.Schedule(start, (function(start,numRan,tickRate,tickCount){
    return function(){
      if(start < 0){ 
        start = 0;
        if(func()){
          return;
        }; 
      } 
      var tickNew = function(){
        numRan++;
        delay = (startTime+tickRate*numRan)-Game.Time();
        /* if((startTime+tickRate*numRan)-Game.Time() < 0){
          $.Msg('[ERROR] Function ' + func + ' taking too long to loop!')
          delay = 0;
        }*/
        $.Schedule(delay, function(){
          if(func()){
            return;
          };
          tickCount--;
          if(tickCount > 0) tickNew();
        });
      };
      tickNew();
    }
  })(start,numRan,tickRate,tickCount));
};

$.Every(0, -1, -10, function(){
  onJavaTimerTick();
});

function OnUnitBuySuccess(data)
{
	myincome = data.income;
	//$.Msg(data);
}
 
function onJavaTimerTick( )
{
	if(Object.keys(unitConf).length == 0)
	{
		GameEvents.SendCustomGameEventToServer( "unitpanel_debug", {thisdata : 1} );
		//$.Msg(unitConf);
	}
	$('#income-label').GetChild(1).text = myincome*10;
	for(var i in unitConf)
	{
		if(currentPage == unitConf[i].page) 
		{
			$(unitConf[i].unitclass).style.visibility = "visible";
		}
		else $(unitConf[i].unitclass).style.visibility = "collapse";
		
		if(Players.GetGold(Players.GetLocalPlayer()) < unitConf[i].cost)
		{ 
			$(unitConf[i].unitclass).style.border = "5px solid #9b0000";
		}
		else
		{
			$(unitConf[i].unitclass).style.border = "5px solid #199900";
			//Game.EmitSound("Quickbuy.Available");
		}
	}
}

function SelectPage(value)
{
	if(currentPage + value > 0 &amp;&amp; currentPage + value <= maxPages)
	{
		currentPage += value;
	}
	else if(currentPage + value == 0) currentPage = maxPages;
	else currentPage = 1;
	Game.EmitSound("ui.shortwhoosh");
	$('#PageLabelNumber').text = currentPage;
}




function OnAllUnitsLoad( event_data )
{
	unitConf = event_data;
	for(var i in unitConf)
	{		
		(function() {
			var m = i;
			var thisclass = $(unitConf[m].unitclass);
			thisclass.GetChild(1).text = unitConf[m].cost;
			var thislocal = $.Localize("#Game_tooltip_income")
					+unitConf[m].income*10
					+"<br>"+$.Localize("tooltip_"+m)+"<br>"
					+"<br>"+$.Localize("Game_tooltip_health")+"<font color='#ffee00'>"+unitConf[m].health+"</font>"
					+"<br>"+$.Localize("Game_tooltip_attackrange")+"<font color='#ffee00'>"+unitConf[m].attackrange+"</font>"
					+"<br>"+$.Localize("Game_tooltip_damage")+"<font color='#ffee00'>"+unitConf[m].mindamage+"-"+unitConf[m].maxdamage+"</font>"
					+"<br>"+$.Localize("Game_tooltip_armor")+"<font color='#ffee00'>"+unitConf[m].armor+"</font>";
					
			if(unitConf[m].ancient == 1) thislocal = $.Localize("#Game_tooltip_ancient") + thislocal;					
			if(unitConf[m].ability1 != "")
			{
				thislocal = thislocal+"<br>"+$.Localize("Game_tooltip_ability") +"<font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability1)+"</font>";
					if(unitConf[m].ability2 != "")
					{
						thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability2)+"</font>";
						if(unitConf[m].ability3 != "")
						{
							thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability3)+"</font>";
							if(unitConf[m].ability4 != "")
							{
								thislocal = thislocal+"<br><font color='#ff0000'>"+$.Localize("DOTA_Tooltip_ability_" + unitConf[m].ability4)+"</font>";
							}
						}
					}
			}				
					
			
			thisclass.SetPanelEvent("onmouseover", function(){ $.DispatchEvent("DOTAShowTextTooltip", thisclass, thislocal);});
			thisclass.SetPanelEvent("onmouseout", function(){ $.DispatchEvent("DOTAHideTextTooltip", thisclass);});				
		})(); 
	}
}

function OnPlayerUnitsLoad(data)
{
	//$.Msg(data);
	var newunits = {};
	for(var i in data)
	{
		(function() {
			var m = i;
			var name = data[m].name
			newunits[name] = {count: data[m].count, unitclass: "unitclass_"+name}
		})();
	}
	ReloadCurrentUnitsPanel(newunits);
}

function ReloadCurrentUnitsPanel(units)
{
	var unitlistlabel = $('#unitlist-label');
	unitlistlabel.RemoveAndDeleteChildren();	
	delete unitHaved;
	unitHaved = units;
	for(i in unitHaved)
	{
		(function() {
			var m = i;
			if(unitHaved[m].count > 0)
			{
				var thispanel = $.CreatePanel("Button", unitlistlabel, unitHaved[m].unitclass );
				var thislabel = $.CreatePanel("Label", thispanel, "label_"+unitHaved[m].unitclass);
				thispanel.SetPanelEvent( 'onactivate', function(){ cmdSellUnit(m, 1);});
				thispanel.AddClass("currentunit_list");
				thislabel.AddClass("currentunit_list_label");
				thispanel.style.border = "2px solid white"; 
				thislabel.text = "$";	
				thislabel.style.visibility = "collapse";
				thispanel.SetPanelEvent("onmouseover", function(){
					thislabel.style.visibility = "visible";
					thispanel.style.border = "3px solid yellow"; 
					$.DispatchEvent("DOTAShowTextTooltip", thispanel, $.Localize("#Game_tooltip_sellunit")+$.Localize("#tooltip_"+m)+"<br>"+ $.Localize("#Game_tooltip_currentunitcount") + unitHaved[m].count);
				});
				thispanel.SetPanelEvent("onmouseout", function(){
					thislabel.style.visibility = "collapse";					
					thispanel.style.border = "2px solid white"; 
					$.DispatchEvent("DOTAHideTextTooltip", thispanel);
				});		
				
				thispanel.style.backgroundSize = "100% 100%";
				thispanel.style.backgroundImage = "url('file://{images}/custom_game/avatar_"+m+".png')";
			}	
		})();
	}
}
 
function cmdSellUnit(unitname, count)
{
	if(unitHaved[unitname].count > 0)
	{
		$.Msg( "EVENT: unit_sell - true ");
		var unitcount = 0;
		for(var i in unitHaved)
		{
			(function(){
				var m = i;
				if(unitHaved[m].count > 0) unitcount += unitHaved[m].count;
				//$.Msg(unitcount);
			})();
		}
		if(unitcount > 4)
		{
			var data = {
				name: unitname,
				count: count
			};
			GameEvents.SendCustomGameEventToServer( "sell_unit", data );
			$.Msg( "EVENT: unit_sell_success - true ");
			Game.EmitSound("General.Sell");
		}
		else 
		{
			Game.EmitSound("General.NoGold");
		}		
	}

}
 
GameEvents.Subscribe( "unitbuy_success", OnUnitBuySuccess);
GameEvents.Subscribe( "load_allunits", OnAllUnitsLoad);
GameEvents.Subscribe( "load_playerunits", OnPlayerUnitsLoad);

function cmdBuyUnit(unitname, count)
{
	$.Msg( "EVENT: unit_buy - true ");
	if(Players.GetGold(Players.GetLocalPlayer()) >= unitConf[unitname].cost)
	{
		
		var data = {
			name: unitname,
			count: count
		};
		GameEvents.SendCustomGameEventToServer( "buy_unit", data );
		$.Msg( "EVENT: unit_buy_success - true ");
		Game.EmitSound("General.Buy");
	}
	else 
	{
		Game.EmitSound("General.NoGold");
	}
}

// UTILS
 
Последнее редактирование модератором:
Окей. Теперь дебажь. Делай print(hero) в OnHeroInGame, перед 209 строкой и перед 222 и смотри что выводится, откуда берутся герои без данных.
 
В общем, как предлагал Илья, решил переделать архитектуру, как бы это не звучало.
Решил сделать таблицу, основанную на playerid, и там хранить данные. Пока всё работает прекрасно. Надеюсь, что ошибка исправлена.

Вывод: не нужно хранить данные в объектах типа hero.
 
MahouShoujo, я рад, что Вы это поняли. Именно поэтому я и обратился за помощью.
 
Primo, я заметил, что ты очень терпеливый парень. Это хорошее качество, молодец.
 
Реклама: