Addressing Desync

Place to get help with not working mods / modding interface.
Post Reply
TheOverwatcher
Burner Inserter
Burner Inserter
Posts: 6
Joined: Thu Jan 24, 2019 2:38 am
Contact:

Addressing Desync

Post by TheOverwatcher »

I'm new to modding but familiar with coding practices. I have recently enjoyed a mod enough to give it an update. The problem my friends and I had when playing is the desync issues when trying to reload a save from the mod. The previous developer hasn't touched it in two years but left a note that they had to fix the issue on saving and during map generation.

I've tried building from the ground up and going with the main advice about desync issues: use global variables in the manner for factorio (global.variable_name). but somehow I've run into more desync issues than trying to update the old mod.

I am looking for any knowledge on how to approach desync issues since, so far, I have read there is not a great way to determine where they originate and there isn't a good way to error log them without just perusing log files for awhile.

Thanks in advance.

User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3700
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: Addressing Desync

Post by DaveMcW »

Search the code for any function named on_load. Delete it.

Search the code for any variable defined outside a function. Delete it and move it to global.variable inside an event function.

TheOverwatcher
Burner Inserter
Burner Inserter
Posts: 6
Joined: Thu Jan 24, 2019 2:38 am
Contact:

Re: Addressing Desync

Post by TheOverwatcher »

I have taken the few variables left outside event functions and defined them in functions. I also took the time reorganize the global variables. I was already aware of on_load and had removed it previously.

Having the recommended desync prevention has no effect on when other players are desync'd. I can narrow down what action causes the desync being completing configuration that "Player 1" does to setup team information, which at this point just updates global variables.

Are there further recommendations to track down why exactly some actions descync players?

Edit: The desync seems almost 'tick' related to on_tick. The outdated mod included a tick_helper I assume to counter this.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Addressing Desync

Post by eduran »

The code you suspect of causing a desync would be useful to help figure out what is wrong with it.
TheOverwatcher wrote:
Thu Jan 24, 2019 2:45 am
The problem my friends and I had when playing is the desync issues when trying to reload a save from the mod.
Does the desync also happen on a fresh game or just when changing from the old mod to your new one?

TheOverwatcher
Burner Inserter
Burner Inserter
Posts: 6
Joined: Thu Jan 24, 2019 2:38 am
Contact:

Re: Addressing Desync

Post by TheOverwatcher »

Any new game causes the issue. There are two scenarios of desync:
  • 1. When using the old mod and loading a save. At this point I don't care about this since I can't get other players not to desync on 2.
    2. After building from the ground up, finishing the intial configuration, and "starting the game" where ticks are used.
I am suspecting the on_tick to be the issue, but I do not know why.

The following is the code from the ticker helper, but is called from control.lua and some other helper lua files.

Code: Select all

function register_tick_helper(f, name)
    global._tick_helper[name] = f;
end

function destroy_tick_helper(name)
    global._tick_helper[name] = nil;
end

function tick_all_helper(tick)
    for k,f in pairs(global._tick_helper) do
        pcall(f,tick);
    end
end

function tick_all_helper_if_valid(tick)
    for k,data in pairs(global._tick_helper_v) do
        if data.next_tick == tick then
            if data.running == false then
                global._tick_helper_v[k].running = true;
                local status, r = pcall(data.if_valid, tick);
                if status and r then
                    pcall(data.tick_helper,tick);
                    global._tick_helper_v[k].running = false;
                    destroy_tick_helper_if_valid(k);
                else
                    global._tick_helper_v[k].next_tick = tick + global.TICK_INTERVAL--data.tick_interval
                    global._tick_helper_v[k].running = false;
                end
            end
        end
    end
end

function register_tick_helper_if_valid(name, tick_helper, tick_interval, if_valid)
    global._tick_helper_v[name] = {
        tick_helper=tick_helper,
        name = name,
        if_valid = if_valid,
        --tick_interval = global.TICK_INTERVAL,
        next_tick = game.tick + global.TICK_INTERVAL,
        running = false
    }
end
function destroy_tick_helper_if_valid(name)
    global._tick_helper_v[name] = nil;
end
Here is the control.lua

Code: Select all

require("constants")
require("utility.event")
require("utility.map_helpers")
require("utility.math")
require("utility.position")
require("utility.table")
require("utility.tick_helper")
require("utility.utilities")
require("utility.configuration_gui")

function areTeamsEnabled()
    return global.TEAMS_ENABLED or false;
end

function setTeamsEnabled()
    global.TEAMS_ENABLED = true;
end

function areTeamsEnabledStartup()
    return global.TEAMS_ENABLED_STARTUP or false;
end

function setTeamsEnabledStartup()
    global.TEAMS_ENABLED_STARTUP = true;
end

function isConfigWritten()
    return global.CONFIG_WRITTEN or false;
end

function setConfigWritten()
    if isConfigWritten() == false then
        global.CONFIG_WRITTEN_TIME = game.tick
    end
    global.CONFIG_WRITTEN = true;
end

function make_forces()
    global.LAST_POINT = nil
    global.USED_POINTS = {}
    global.s = game.surfaces["nauvis"]
    table.each(global.forcesData, function(data, k)
        if data.cords == nil then
            global.RANDOM_POINT = MM_get_random_point(global.USED_POINTS, global.LAST_POINT);
            global.LAST_POINT = global.RANDOM_POINT;

			global.RANDOM_POINT.x = Rnd(220,1060)
			global.RANDOM_POINT.y = Rnd(220,1060)
			
            global.forcesData[k].team_x = global.RANDOM_POINT.x
            global.forcesData[k].team_y = global.RANDOM_POINT.y
            global.forcesData[k].team_position = { global.forcesData[k].team_x, global.forcesData[k].team_y }
            global.forcesData[k].cords = { x = global.forcesData[k].team_x, y = global.forcesData[k].team_y }
            global.forcesData[k].team_area = { { global.forcesData[k].team_x - global.distance, global.forcesData[k].team_y - global.distance }, { global.forcesData[k].team_x + global.distance, global.forcesData[k].team_y + global.distance } }
            global.LAST_POINT = global.forcesData[k].cords
            table.insert(global.USED_POINTS, global.LAST_POINT)
            --        logTable(global.forcesData[k],k);
        end
        MM_create_force(data);
    end);
end

function Rnd(min,max,adseed)

	min = min or 1
	max = max or 1
	adseed = adseed or game.tick
	
	global.adseed = global.adseed or 42
	global.adseed = global.adseed + adseed
	global.adseed = global.adseed < 0 and 0 - global.adseed or global.adseed
	global.adseed = global.adseed >= 2^32-1 and 1 or global.adseed
	
	global.seed = game.tick + math.floor(tonumber(tostring({}):sub(8,-4))) + adseed
	global.seed = global.seed < 0 and 0 - global.seed or global.seed
	global.seed = global.seed >= 2^32-1 and game.tick or global.seed
	
	if not global.generator then 
		global.generator = game.create_random_generator(global.seed)
	else
		global.generator.re_seed(global.seed)
	end
	
	return math.min(max, math.max(min, math.floor(global.generator(min, math.max(min, max + global.adseed)) % max) ) )
end

function set_spawns()
	global.s = game.surfaces['nauvis'];
    game.daytime = 0.9

    table.each(global.forcesData, function(data)
        MM_set_spawns(data);
    end)
end

function set_starting_areas()
	global.s = game.surfaces['nauvis'];

    table.each(global.forcesData, function(data)
        MM_set_starting_area(data);
    end)
end

function make_team_option(player)
	if global.player.gui.left.choose_team == nil then
		global.frame = global.player.gui.left.add { name = "choose_team", type = "frame", direction = "vertical", caption = { "resources.team-choose" } }

		table.each(global.forcesData, function(data)
			global.frame.add { type = "button", caption = { 'resources.team-join', data.title }, name = 'choose_team_' .. data.name }.style.font_color = data.color
		end)
		global.frame.add { type = "button", caption = { "resources.team-auto-assign"}, name = "autoAssign" }
		global.frame.add { type = "button", caption = { "resources.team-check-number" }, name = "team_number_check" }.style.font_color = { r = 0.9, g = 0.9, b = 0.9 }
	end
end

function make_lobby()
	game.create_surface("Lobby", { width = 96, height = 32, starting_area = "big", water = "none" })
end

function on_init(event)
	make_lobby()
	
	-- global information modified from initial config
	if global.forcesData == nil then
		global.forcesData = {}
	end
	
	if global.points == nil then
		global.points = {
			startingAreaRadius = 50, -- in this radius every tree will be destroyed
			start = {
				-- defines start point of map			
				x = 0,--math.random(220, 1060),
				y = 0--math.random(220, 1060)  
			}, -- defines distance between spawn points for teams
			distance = {
				min = 1200,
				max = 1800,
			},
			numberOfTrysBeforeError = 40 -- this is just a helper value (if we try to locate a non colliding point, we try it max XX times before we abort)
		}
	end

	if global.distance == nil then
		global.distance = 60 * 3
	end
	
	if global.big_distance == nil then
		global.big_distance = 40 * 3 * 3
	end
	
	if global.researchMessage == nil then
		global.researchMessage = {
			enabled = true
		}
	end
	
	if global.attackMessage == nil then
		global.attackMessage = {
			enabled = true,
			interval = 5*MINUTES
		}
	end
	
	if global.enableTeams == nil then
		global.enableTeams = {
			after = 30 * SECONDS, -- teams are eneabled after XX Seconds
			messageInterval = 10 * SECONDS -- message interval /(when are the teams unlocked)
		}
	end
	
	if global.teamBalance == nil then
		global.teamBalance = {
			enabled = true, -- wether team balance should be enabled or not
		}
	end
	
	if global.teamMessageGui == nil then
		global.teamMessageGui = {
			enabled = true
		}
	end
	
	if global.allianceGui == nil then
		global.allianceGui = {
			enabled = true,
			changeAbleTick = 5 * MINUTES -- alliances could only be changed every XXX ticks
		}
	end
	
	global._tick_helper = {};
	global._tick_helper_v = {};
	
end

function on_player_created(event)

	--Get the player that just joined
	global.player = game.players[event.player_index]
	--Take them to the lobby
	global.player.teleport({ 0, 8 }, game.surfaces["Lobby"])

	PrintToAllPlayers("A challenger has approached.")
	
	--If the host player and configuration was not set.
	if global.player.index == 1 and not isConfigWritten() then
		PrintToAllPlayers('Creating configuration gui');
        ConfigurationGui:createConfigurationGui(global.player);
	end
	
	--Print messages to new arrivals
	if areTeamsEnabled() then
		PrintToAllPlayers({"resources.please-select-team-msg"})
		make_team_option(global.player)
	else 
		PrintToAllPlayers({"resources.teams-have-not-been-set-msg"})
	end
	
	global.player.get_inventory(defines.inventory.player_ammo).clear();
    global.player.get_inventory(defines.inventory.chest).clear();
    global.player.get_inventory(defines.inventory.player_guns).clear();
    global.player.get_inventory(defines.inventory.player_main).clear();
    global.player.get_inventory(defines.inventory.player_quickbar).clear();
	
end

script.on_init(on_init);
script.on_event(defines.events.on_player_created, on_player_created);

function after_lobby_tick(event)
    tick_all_helper(event.tick);
    tick_all_helper_if_valid(event.tick);
end

function triggerTeamsEnabling()
    global.TRIGGER_TEAMS_SECONDS = 10;
	global.TICK_INTERVAL = global.TRIGGER_TEAMS_SECONDS * SECONDS;
    PrintToAllPlayers({ "resources.teams-enable-wait-to-be-charted" })
    --        PrintToAllPlayers('Wait for team areas to be charted');
    global.checked_teams = {};
    global.if_valid = function(tick)
		PrintToAllPlayers("checking if valid")
        for _, data in pairs(global.forcesData) do
			PrintToAllPlayers(checked_teams[data.name])
            if checked_teams[data.name] == nil then
                PrintToAllPlayers("Checked teams is nil")
				if is_area_charted(round_area_to_chunk_save(SquareArea(data.cords, global.big_distance)), global.s) == false then
				PrintToAllPlayers("is area charted check")
                --PrintToAllPlayers({ 'resources.teams-enable-not-charted-yet', data.title, seconds })
					return false;
                end
				PrintToAllPlayers("set true for team")
                --PrintToAllPlayers({ 'resources.team-area-charted', data.title })
                checked_teams[data.name] = true;
            end
        end
        return true;
    end

    global.tick_helper = function(tick)
        table.each(global.forcesData, function(data)
            MM_set_spawns(data);
            MM_set_starting_area(data);
        end)
        setTeamsEnabled();
        table.each(game.players, function(p)
            make_team_option(p);
        end)
    end
    register_tick_helper_if_valid('CREATE_TEAMS', global.tick_helper, global.tick_interval, global.if_valid);
end

function putPlayerInTeam(player, forceData)
	global.s = game.surfaces['nauvis'];
    global.player.teleport(game.forces[forceData.cName].get_spawn_position(global.s), global.s)
    global.player.color = forceData.color
    global.player.force = game.forces[forceData.cName]
    player.gui.left.choose_team.destroy()
    global.player.insert { name = "iron-plate", count = 8 }
    global.player.insert { name = "pistol", count = 1 }
    global.player.insert { name = "firearm-magazine", count = 10 }
    global.player.insert { name = "burner-mining-drill", count = 1 }
    global.player.insert { name = "stone-furnace", count = 1 }
    Alliance:make_alliance_overlay_button(player);
    TeamChat:make_team_chat_button(player);
    PrintToAllPlayers({ 'teams.player-msg-team-join', global.player.name, forceData.title })
end

function couldJoinIntoForce(forceName)
    if global.teamBalance.enabled == false then
        return true;
    end

    global.check = {}
    global.lastValue = 0;
    global.onlyOne = false;
    table.each(global.forcesData, function(data)
        global.c = 0;
        table.each(game.players, function(p)
            if data.cName == global.player.force.name then c = c + 1 end
        end)
        check[data.cName] = global.c;
        if global.lastValue == global.c then -- check if all teams have the same amount of players
            global.onlyOne = true;
        else
            global.onlyOne = false;
        end
        global.lastValue = global.c
    end)
    if global.onlyOne == true then -- if all teams have the same amount of players, then it is possible to join this team
        return true;
    end
    for k,v in spairs(check) do
        return check[forceName] < v -- only join, if wanted force has fewer amount of players as the largest team
    end

    return true;
end

function lobby_tick(event)
    tick_all_helper(event.tick);
    tick_all_helper_if_valid(event.tick);


    if game.tick >= global.CONFIG_WRITTEN_TIME + global.enableTeams.after then
        -- fix if game is saved and reload during generation
		PrintToAllPlayers("Are teams enabled:")
		PrintToAllPlayers(areTeamsEnabledStartup())
		--teamsEnablingStarted = areTeamsEnabledStartup();
        if areTeamsEnabledStartup() == false then
            --teamsEnablingStarted = true;
			setTeamsEnabledStartup();
            triggerTeamsEnabling();
        end
    elseif game.tick <= global.CONFIG_WRITTEN_TIME + global.enableTeams.after and game.tick % global.enableTeams.messageInterval == 0 then
        global.enableTick = (global.CONFIG_WRITTEN_TIME + global.enableTeams.after) - game.tick;
        global.seconds = Math.round(global.enableTick / SECONDS);
        PrintToAllPlayers({ 'resources.teams-enable-in', global.seconds })
    end


    if (areTeamsEnabled()) then
        script.on_event(defines.events.on_tick, nil)
        script.on_event(defines.events.on_tick, after_lobby_tick)
    end
end

script.on_event(defines.events.on_tick,
	function(event)

		if isConfigWritten() then
			--PrintToAllPlayers("on_tick event - config written")
			script.on_event(defines.events.on_tick, lobby_tick);
		end
		
		--fire armor
		if event.tick % 60 == 0 then --common trick to reduce how often this runs, we don't want it running every tick, just 1/second
         for index,player in pairs(game.connected_players) do  --loop through all online players on the server
            
            --if they're wearing our armor
            if global.player.character and global.player.get_inventory(defines.inventory.player_armor).get_item_count("fire-armor") >= 1 then
               --create the fire where they're standing
               global.player.surface.create_entity{name="fire-flame", position=global.player.position, force="neutral"} 
            end
         end
      end
		
	end
)

--Handling the gui clicks
script.on_event(defines.events.on_gui_click, 
function(event)

	global.s = game.surfaces.nauvis;
    global.element = event.element
    if global.element.valid ~= true then
        return;
    end

    global.eventName = global.element.name;

	global.player = game.players[event.player_index];	
	--Frame related
	if global.eventName == 'next_step' then
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].saveStep, global.player);
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].destroyStep, global.player);
        ConfigurationGui.currentStep = ConfigurationGui.steps[ConfigurationGui.currentStep].nextStep;
        global.g = ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].createStep, global.player);
        ConfigurationGui:createNextAndPrev(global.g);
    elseif global.eventName == 'prev_step' then
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].saveStep, global.player);
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].destroyStep, global.player);
        ConfigurationGui.currentStep = ConfigurationGui.steps[ConfigurationGui.currentStep].prevStep;
        global.g = ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].createStep, global.player);
        ConfigurationGui:createNextAndPrev(global.g);
	elseif global.eventName == 'start_game' then
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].saveStep, global.player);
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].destroyStep, global.player);
        PrintToAllPlayers({ "lobby.lobby-msg-config-finished", game.players[1].name })
        make_forces()
        setConfigWritten();
	elseif global.eventName == 'force_cancel' then
        if global.player.gui.center.create_force ~= nil and global.player.gui.center.create_force.valid then
           global.player.gui.center.create_force.destroy();
        end
        ConfigurationGui:createNextAndPrev(ConfigurationGui.steps.teams.createFrame(global.player));
    --Force related
	elseif global.eventName == 'force_save' then
        if global.player.gui.center.create_force ~= nil and global.player.gui.center.create_force.valid then
            if global.player.gui.center.create_force.caption ~= nil then
                global.forcesData[global.player.gui.center.create_force.caption] = nil;
            end
            global.forceData = ConfigurationGui.steps.teams.getForceData(global.player.gui.center.create_force);
            if global.forceData.cName ~= '' then
                global.forcesData[global.forceData.cName] = global.forceData;
                global.player.gui.center.create_force.destroy();
            end
        end
        ConfigurationGui:createNextAndPrev(ConfigurationGui.steps.teams.createFrame(global.player));
    elseif global.eventName == 'force_remove' then
        global.parent = global.element.parent;
        table.each(global.forcesData, function(forceData, k)
            if global.parent.name == 'force_frame_' .. forceData.name then
                global.forcesData[k] = nil;
            end
        end)
        ConfigurationGui:createNextAndPrev(ConfigurationGui.steps.teams.createFrame(global.player));
    elseif string.match(global.eventName,'force_edit_(.*)') ~= nil then
        table.each(global.forcesData, function(forceData)
            if global.element.valid and global.element.name == 'force_edit_' .. forceData.name then
                if global.player.gui.center.teams_gui ~= nil then
                    global.player.gui.center.teams_gui.destroy();
                end
                ConfigurationGui.steps.teams.createForceGuiWithData(global.player, forceData);
                return;
            end
        end)
    elseif global.eventName == 'force_new' then
        if global.player.gui.center.teams_gui ~= nil then
            global.player.gui.center.teams_gui.destroy();
        end
        ConfigurationGui.steps.teams.createForceGui(p);
		
	--team realated
	elseif global.eventName == 'team_number_check' then
        table.each(global.forcesData, function(data)
            global.c = 0;
            table.each(game.players, function(p)
                if data.cName == global.player.force.name then global.c = global.c + 1 end
            end)
            global.player.print({ 'resources.player-msg-team-number', data.title, global.c })
        end)
    elseif global.eventName == 'autoAssign' then
        global.check = {}
        table.each(global.forcesData, function(data)
            global.c = 0;
            table.each(game.players, function(p)
                if data.cName == global.p.force.name then global.c = global.c + 1 end
            end)
            global.check[data.cName] = global.c;
        end)
        for k,v in spairs(global.check, function (t,a,b) return t[a] < t[b] end) do
            putPlayerInTeam(global.player, global.forcesData[k]);
            break
        end
    elseif string.match(global.eventName, 'choose_team_.*') ~= nil then
        table.each(global.forcesData, function(data)
            if global.eventName == 'choose_team_' .. data.name then
                if couldJoinIntoForce(data.cName) then
                    putPlayerInTeam(global.player, data);
                else
                    global.player.print( { 'resources.player-msg-could-not-join', data.title } )
                end
            end
        end)
	end
end
) --end event handler for gui click

-- Normal Events for game changes (launching rocket, dieing)
script.on_event(defines.events.on_rocket_launched, function(event)
    local force = event.rocket.force
    if event.rocket.get_item_count("satellite") > 0 then
        if global.satellite_sent == nil then
            global.satellite_sent = {}
        end
        if global.satellite_sent[force.name] == nil then
            game.set_game_state { game_finished = true, player_won = true, can_continue = true }
            global.satellite_sent[force.name] = 1
        else
            global.satellite_sent[force.name] = global.satellite_sent[force.name] + 1
        end
        for index, player in pairs(force.players) do
            if global.player.gui.left.rocket_score == nil then
                local frame = global.player.gui.left.add { name = "rocket_score", type = "frame", direction = "horizontal", caption = { "score" } }
                frame.add { name = "rocket_count_label", type = "label", caption = { "", { "rockets-sent" }, "" } }
                frame.add { name = "rocket_count", type = "label", caption = "1" }
            else
                global.player.gui.left.rocket_score.rocket_count.caption = tostring(global.satellite_sent[force.name])
            end
        end
    else
        if (#game.players <= 1) then
            game.show_message_dialog { text = { "gui-rocket-silo.rocket-launched-without-satellite" } }
        else
            for index, player in pairs(force.players) do
                global.player.print({ "gui-rocket-silo.rocket-launched-without-satellite" })
            end
        end
    end
end)

script.on_event(defines.events.on_research_started, function(event)
    if global.researchMessage.enabled then
        local force = event.research.force
        PrintToAllPlayers({ 'teams.team-research-start', global.forcesData[force.name].title, { 'research-name' .. event.research.name } })
        --    ResearchNotification.log('Team ' .. force.name .. ' has starting research ' .. event.research.name, force.name);
    end
end)
script.on_event(defines.events.on_research_finished, function(event)
    if global.researchMessage.enabled then
        local force = event.research.force
        PrintToAllPlayers({ 'teams.team-research-end', global.forcesData[force.name].title, { 'research-name' .. event.research.name } })
    end
end)


script.on_event(defines.events.on_entity_died, function(event)
    if global.attackMessage.enabled then
        global.entity = event.entity;
        global.force = event.force;
        if global.force ~= nil and global.entity.force.name ~= 'neutral' then
            if global.entity.force.name == 'enemy' then
                if global.entity.name ~= "spitter-spawner"
                        and global.entity.name ~= "biter-spawner"
                        and global.entity.name ~= "small-worm-turret"
                        and global.entity.name ~= "medium-worm-turret"
                        and global.entity.name ~= "big-worm-turret"
                then
                    return
                end
            end
            if global.entity.force ~= nil and global.entity.force ~= global.force then
                global.attacked = global.entity.force.name;
                global.attackedFrom = global.force.name;
                if global.attacked == 'enemy' then
                    global.attacked = 'Aliens'
                end
                if global.attackedFrom == 'enemy' then
                    global.attackedFrom = 'Aliens';
                end
                PrintToAllPlayers({ 'teams.team-attacked-by-team', global.attacked, global.attackedFrom })
            end
        end
    end
end)
For reference, the gui that configuration setup is through is laid out in the following configuration_gui.lua

Code: Select all

ConfigurationGui = {}


ConfigurationGui.steps = {
    teams = {
        prevStep = nil,
        nextStep = 'enable',
        caption = { 'resources-gui.forces-gui-caption' },
        
		createFrame = function(player)
			--if it already exists, close it
			if player.gui.center.teams_gui ~= nil then
                player.gui.center.teams_gui.destroy();
            end
			
			--create the frame
			global.frame = ConfigurationGui:createFrame(player.gui.center, 'teams_gui', { 'resources-gui.create-gui-caption' });
			
			PrintToAllPlayers("Debug for table each.")
			
			table.each(global.forcesData, function(forceData)
                global.forceFrame = global.frame.add {
                    name = 'force_frame_' .. forceData.name,
                    type = 'flow',
                    direction = 'horizontal'
                }
                global.forceFrame.add {
                    type = 'label',
                    --name = 'name', @2.1.1
                    caption = forceData.title
                }.style.font_color = forceData.color
                global.forceFrame.add {
                    type = 'button',
                    name = 'force_edit_' .. forceData.name,
                    caption = { 'resources-gui.force-edit-caption' }
                }
                if game.forces[forceData.name] == nil then
                    global.forceFrame.add {
                        type = 'button',
                        name = 'force_remove',
                        caption = { 'resources-gui.force-remove-caption' }
                    }
                end
            end)
			global.frame.add {
                type = 'button',
                name = 'force_new',
                caption = { 'resources-gui.force-new-caption' }
            }
            return global.frame;
		end,
		createForceGuiWithData = function(player, forceData)
            if global.player.gui.center.create_force ~= nil then
                global.player.gui.center.create_force.destroy();
            end
            global.frame = ConfigurationGui:createFrame(global.player.gui.center, 'create_force', { 'resources-gui.create-gui-caption' });
            if forceData.cName ~= '' then
                global.frame.caption = forceData.cName;
            end
            for k, v in pairs({
                'name',
                'title',
                'cName'
            }) do
                global.flow = global.frame.add {
                    type = 'flow',
                    name = "force_" .. v,
                    direction = 'vertical'
                }
                global.flow.add {
                    type = "label",
                    caption = { 'resources-gui.force-' .. v .. '-caption' }
                }
                global.flow.add {
                    name = "textfield",
                    type = "textfield",
                    text = forceData[v],
                }
            end
            global.color = global.frame.add {
                type = 'frame',
                direction = 'vertical',
                name = 'color',
                caption = { 'resources-gui.force-color-caption' }
            }

            global.forceColor = ConfigurationGui:colorToRgb(forceData.color)
            for k, v in pairs(global.forceColor) do
                global.flow = global.color.add {
                    type = 'flow',
                    direction = 'horizontal',
                    name = k
                }
                global.flow.add {
                    type = "label",
                    caption = { 'resources-gui.force-color-' .. k .. '-caption' }
                }
                global.flow.add {
                    name = "textfield",
                    text = v,
                    type = "textfield",
                }
            end

            global.frame.add {
                type = 'button',
                name = 'force_cancel',
                caption = { 'resources-gui.force-cancel-caption' }
            }

            global.frame.add {
                type = 'button',
                name = 'force_save',
                caption = { 'resources-gui.force-save-caption' }
            }
        end,
        createForceGui = function(player)
            local forceData = {
                name = "",
                title = "",
                cName = '',
                color = { r = 1, g = 1, b = 1, a = 1 }
            }
            ConfigurationGui.steps.teams.createForceGuiWithData(player, forceData);
        end,
		getForceData = function(frame)
            return {
                name = frame.force_name.textfield.text,
                title = frame.force_title.textfield.text,
                cName = frame.force_cName.textfield.text,
                color = ConfigurationGui:rgbToColor({
                    r = frame.color.r.textfield.text,
                    g = frame.color.g.textfield.text,
                    b = frame.color.b.textfield.text,
                    a = frame.color.a.textfield.text
                })
            }
        end,
		createStep = function(player)
            return ConfigurationGui.steps.teams.createFrame(player);
        end,
        saveStep = function(player) end,
        destroyStep = function(player)
            if player.gui.center.teams_gui ~= nil then
                player.gui.center.teams_gui.destroy();
            end
        end
    },
    enable = {
        prevStep = 'teams',
        nextStep = 'points',
        caption = { 'enable-gui.caption' },
        createStep = function(player)
            global.frame = ConfigurationGui:createFrame(player.gui.center, 'enable_gui', { 'enable-gui.caption' });
            --table.each({
             --   "attackMessage",
            --    "researchMessage",
            --    "teamMessageGui",
            --    "allianceGui",
           --     "teamBalance"
           -- },
                --function(name)
                    ConfigurationGui:createCheckboxFlow(global.frame, "attackMessage", { 'enable-gui.label-' .. "attackMessage" }, global.attackMessage.enabled)
                    ConfigurationGui:createCheckboxFlow(global.frame, "researchMessage", { 'enable-gui.label-' .. "researchMessage" }, global.researchMessage.enabled)
                    ConfigurationGui:createCheckboxFlow(global.frame, "teamMessageGui", { 'enable-gui.label-' .. "teamMessageGui" }, global.teamMessageGui.enabled)
                    ConfigurationGui:createCheckboxFlow(global.frame, "allianceGui", { 'enable-gui.label-' .. "allianceGui" }, global.allianceGui.enabled)
                    ConfigurationGui:createCheckboxFlow(global.frame, "teamBalance", { 'enable-gui.label-' .. "teamBalance" }, global.teamBalance.enabled)
                --end
				--)
            return global.frame
        end,
        saveStep = function(player)
            --table.each({
             --   "attackMessage",
             --   "researchMessage",
            --    "teamMessageGui",
            --    "allianceGui",
             --   "teamBalance"
            --},
                --function(name)
					global.attackMessage.enabled = player.gui.center.enable_gui["attackMessage"].checkbox.state
					global.researchMessage.enabled = player.gui.center.enable_gui["researchMessage"].checkbox.state
					global.teamMessageGui.enabled = player.gui.center.enable_gui["teamMessageGui"].checkbox.state
					global.allianceGui.enabled = player.gui.center.enable_gui["allianceGui"].checkbox.state
					global.teamBalance.enabled = player.gui.center.enable_gui["teamBalance"].checkbox.state
					
               -- end
			 --  )
        end,
        destroyStep = function(player)
            if player.gui.center.enable_gui ~= nil then
                player.gui.center.enable_gui.destroy();
            end
        end
    },
    points = {
        prevStep = 'enable',
        nextStep = nil,
        caption = { 'point-gui.caption' },
        createStep = function(player)
            global.frame = ConfigurationGui:createFrame(player.gui.center, 'point_gui', { 'point-gui.caption' });

            ConfigurationGui:createTextFieldFlow(global.frame, 'startingAreaRadius', { 'point-gui.label-startingAreaRadius' }, global.points.startingAreaRadius);
            ConfigurationGui:createTextFieldFlow(global.frame, 'pointsMin', { 'point-gui.label-distance-min' }, global.points.distance.min);
            ConfigurationGui:createTextFieldFlow(global.frame, 'pointsMax', { 'point-gui.label-distance-max' }, global.points.distance.max);
            ConfigurationGui:createTextFieldFlow(global.frame, 'd', { 'point-gui.label-distance' }, global.distance);
            ConfigurationGui:createTextFieldFlow(global.frame, 'bd', { 'point-gui.label-big-distance' }, global.big_distance);

            return global.frame;
        end,
        saveStep = function(player)
            global.gui = player.gui.center.point_gui;
            global.distance = tonumber(global.gui.d.textfield.text);
            global.big_distance = tonumber(global.gui.bd.textfield.text);
            global.points.startingAreaRadius = tonumber(global.gui.startingAreaRadius.textfield.text);
            global.points.distance.min = tonumber(global.gui.pointsMin.textfield.text);
            global.points.distance.max = tonumber(global.gui.pointsMax.textfield.text);
        end,
        destroyStep = function(player)
            if player.gui.center.point_gui ~= nil then
                player.gui.center.point_gui.destroy();
            end
        end
    }
}

function ConfigurationGui:createNextAndPrev(gui)
    global.f = gui.add {
        type = 'flow',
        direction = 'horizontal'
    }

    global.s = ConfigurationGui.steps[ConfigurationGui.currentStep];

    if global.s.prevStep ~= nil then
        global.f.add {
            type = 'button',
            name = 'prev_step',
            caption = { '', '< ', ConfigurationGui.steps[global.s.prevStep].caption }
        }
    end

    if global.s.nextStep == nil then
        if global.forcesData ~= nil and table.length(global.forcesData) > 0 then
            global.f.add {
                type = 'button',
                name = 'start_game',
                caption = { 'config-gui.start' }
            }
        else
            global.f.add {
                type = 'label',
                caption = { 'config-gui.min-one-team' }
            }
        end
    else
        global.f.add {
            type = 'button',
            name = 'next_step',
            caption = { '', ConfigurationGui.steps[global.s.nextStep].caption, ' >' }
        }
    end
end

ConfigurationGui.currentStep = 'teams';

function ConfigurationGui:createConfigurationGui(player)
    -- init first step
    global.g = ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].createStep, player);

    ConfigurationGui:createNextAndPrev(global.g);
end

function ConfigurationGui:try(f, ...)
    global.status, r = pcall(f, ...);
    if not global.status then
        PrintToAllPlayers(r);
    end
    return r;
end

function ConfigurationGui:createFrame(parent, name, caption)
    return parent.add {
        type = 'frame',
        name = name,
        direction = 'vertical',
        caption = caption
    }
end

function ConfigurationGui:createTextFieldFlow(gui, name, caption, value)
    global.flow = gui.add {
        type = 'flow',
        direction = 'horizontal',
        name = name
    }
    global.flow.add {
        type = "label",
        caption = caption
    }
    global.flow.add {
        name = "textfield",
        type = "textfield",
        text = value,
    }
end

function ConfigurationGui:createCheckboxFlow(gui, name, caption, value)
    global.flow = gui.add {
        type = 'flow',
        direction = 'horizontal',
        name = name
    }
    global.flow.add {
        type = "label",
        caption = caption
    }
    global.flow.add {
        name = "checkbox",
        type = "checkbox",
        state = value,
    }
end

function ConfigurationGui:colorToRgb(color)
    return {
        r = color.r * 255,
        g = color.g * 255,
        b = color.b * 255,
        a = color.a
    }
end

function ConfigurationGui:rgbToColor(rgb)
    return {
        r = rgb.r / 255,
        g = rgb.g / 255,
        b = rgb.b / 255,
        a = rgb.a
    }
end

User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3700
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: Addressing Desync

Post by DaveMcW »

You call

Code: Select all

script.on_event(defines.events.on_tick
... in 4 different places, without sufficient desync protection. (This is one of the three specific reasons where on_load is actually needed). I recommend reducing it to 1 call.

TheOverwatcher
Burner Inserter
Burner Inserter
Posts: 6
Joined: Thu Jan 24, 2019 2:38 am
Contact:

Re: Addressing Desync

Post by TheOverwatcher »

Ended up replacing the calls down to just one. The desync still happens when the on_tick event occurs.

Code: Select all

require("constants")
require("utility.event")
require("utility.map_helpers")
require("utility.math")
require("utility.position")
require("utility.table")
require("utility.tick_helper")
require("utility.utilities")
require("utility.configuration_gui")

function areTeamsEnabled()
    return global.TEAMS_ENABLED or false;
end

function setTeamsEnabled()
    global.TEAMS_ENABLED = true;
end

function areTeamsEnabledStartup()
    return global.TEAMS_ENABLED_STARTUP or false;
end

function setTeamsEnabledStartup()
    global.TEAMS_ENABLED_STARTUP = true;
end

function isConfigWritten()
    return global.CONFIG_WRITTEN or false;
end

function setConfigWritten()
    if isConfigWritten() == false then
        global.CONFIG_WRITTEN_TIME = game.tick
    end
    global.CONFIG_WRITTEN = true;
end

function make_forces()
    global.LAST_POINT = nil
    global.USED_POINTS = {}
    global.s = game.surfaces["nauvis"]
    table.each(global.forcesData, function(data, k)
        if data.cords == nil then
            global.RANDOM_POINT = MM_get_random_point(global.USED_POINTS, global.LAST_POINT);
            global.LAST_POINT = global.RANDOM_POINT;

			global.RANDOM_POINT.x = Rnd(220,1060)
			global.RANDOM_POINT.y = Rnd(220,1060)
			
            global.forcesData[k].team_x = global.RANDOM_POINT.x
            global.forcesData[k].team_y = global.RANDOM_POINT.y
            global.forcesData[k].team_position = { global.forcesData[k].team_x, global.forcesData[k].team_y }
            global.forcesData[k].cords = { x = global.forcesData[k].team_x, y = global.forcesData[k].team_y }
            global.forcesData[k].team_area = { { global.forcesData[k].team_x - global.distance, global.forcesData[k].team_y - global.distance }, { global.forcesData[k].team_x + global.distance, global.forcesData[k].team_y + global.distance } }
            global.LAST_POINT = global.forcesData[k].cords
            table.insert(global.USED_POINTS, global.LAST_POINT)
            --        logTable(global.forcesData[k],k);
        end
        MM_create_force(data);
    end);
end

function Rnd(min,max,adseed)

	min = min or 1
	max = max or 1
	adseed = adseed or game.tick
	
	global.adseed = global.adseed or 42
	global.adseed = global.adseed + adseed
	global.adseed = global.adseed < 0 and 0 - global.adseed or global.adseed
	global.adseed = global.adseed >= 2^32-1 and 1 or global.adseed
	
	global.seed = game.tick + math.floor(tonumber(tostring({}):sub(8,-4))) + adseed
	global.seed = global.seed < 0 and 0 - global.seed or global.seed
	global.seed = global.seed >= 2^32-1 and game.tick or global.seed
	
	if not global.generator then 
		global.generator = game.create_random_generator(global.seed)
	else
		global.generator.re_seed(global.seed)
	end
	
	return math.min(max, math.max(min, math.floor(global.generator(min, math.max(min, max + global.adseed)) % max) ) )
end

function set_spawns()
	global.s = game.surfaces['nauvis'];
    game.daytime = 0.9

    table.each(global.forcesData, function(data)
        MM_set_spawns(data);
    end)
end

function set_starting_areas()
	global.s = game.surfaces['nauvis'];

    table.each(global.forcesData, function(data)
        MM_set_starting_area(data);
    end)
end

function make_team_option(player)
	if global.player.gui.left.choose_team == nil then
		global.frame = global.player.gui.left.add { name = "choose_team", type = "frame", direction = "vertical", caption = { "resources.team-choose" } }

		table.each(global.forcesData, function(data)
			global.frame.add { type = "button", caption = { 'resources.team-join', data.title }, name = 'choose_team_' .. data.name }.style.font_color = data.color
		end)
		global.frame.add { type = "button", caption = { "resources.team-auto-assign"}, name = "autoAssign" }
		global.frame.add { type = "button", caption = { "resources.team-check-number" }, name = "team_number_check" }.style.font_color = { r = 0.9, g = 0.9, b = 0.9 }
	end
end

function make_lobby()
	game.create_surface("Lobby", { width = 96, height = 32, starting_area = "big", water = "none" })
end

function on_init(event)
	make_lobby()
	
	-- global information modified from initial config
	if global.forcesData == nil then
		global.forcesData = {}
	end
	
	if global.points == nil then
		global.points = {
			startingAreaRadius = 50, -- in this radius every tree will be destroyed
			start = {
				-- defines start point of map			
				x = 0,--math.random(220, 1060),
				y = 0--math.random(220, 1060)  
			}, -- defines distance between spawn points for teams
			distance = {
				min = 1200,
				max = 1800,
			},
			numberOfTrysBeforeError = 40 -- this is just a helper value (if we try to locate a non colliding point, we try it max XX times before we abort)
		}
	end

	if global.distance == nil then
		global.distance = 60 * 3
	end
	
	if global.big_distance == nil then
		global.big_distance = 40 * 3 * 3
	end
	
	if global.researchMessage == nil then
		global.researchMessage = {
			enabled = true
		}
	end
	
	if global.attackMessage == nil then
		global.attackMessage = {
			enabled = true,
			interval = 5*MINUTES
		}
	end
	
	if global.enableTeams == nil then
		global.enableTeams = {
			after = 30 * SECONDS, -- teams are eneabled after XX Seconds
			messageInterval = 10 * SECONDS -- message interval /(when are the teams unlocked)
		}
	end
	
	if global.teamBalance == nil then
		global.teamBalance = {
			enabled = true, -- wether team balance should be enabled or not
		}
	end
	
	if global.teamMessageGui == nil then
		global.teamMessageGui = {
			enabled = true
		}
	end
	
	if global.allianceGui == nil then
		global.allianceGui = {
			enabled = true,
			changeAbleTick = 5 * MINUTES -- alliances could only be changed every XXX ticks
		}
	end
end

function on_player_created(event)

	--Get the player that just joined
	global.player = game.players[event.player_index]
	--Take them to the lobby
	global.player.teleport({ 0, 8 }, game.surfaces["Lobby"])

	PrintToAllPlayers("A challenger has approached.")
	
	--If the host player and configuration was not set.
	if global.player.index == 1 and not isConfigWritten() then
		PrintToAllPlayers('Creating configuration gui');
        ConfigurationGui:createConfigurationGui(global.player);
	end
	
	--Print messages to new arrivals
	if areTeamsEnabled() then
		PrintToAllPlayers({"resources.please-select-team-msg"})
		make_team_option(global.player)
	else 
		PrintToAllPlayers({"resources.teams-have-not-been-set-msg"})
	end
	
	global.player.get_inventory(defines.inventory.player_ammo).clear();
    global.player.get_inventory(defines.inventory.chest).clear();
    global.player.get_inventory(defines.inventory.player_guns).clear();
    global.player.get_inventory(defines.inventory.player_main).clear();
    global.player.get_inventory(defines.inventory.player_quickbar).clear();
	
end

script.on_init(on_init);
script.on_event(defines.events.on_player_created, on_player_created);

function triggerTeamsEnabling()
    global.TRIGGER_TEAMS_SECONDS = 10;
	global.TICK_INTERVAL = global.TRIGGER_TEAMS_SECONDS * SECONDS;
    PrintToAllPlayers({ "resources.teams-enable-wait-to-be-charted" })
    --        PrintToAllPlayers('Wait for team areas to be charted');
    global.checked_teams = {};
    global.if_valid = function(tick)
		PrintToAllPlayers("checking if valid")
        for _, data in pairs(global.forcesData) do
			PrintToAllPlayers(checked_teams[data.name])
            if checked_teams[data.name] == nil then
                PrintToAllPlayers("Checked teams is nil")
				if is_area_charted(round_area_to_chunk_save(SquareArea(data.cords, global.big_distance)), s) == false then
				PrintToAllPlayers("is area charted check")
                --PrintToAllPlayers({ 'resources.teams-enable-not-charted-yet', data.title, seconds })
					return false;
                end
				PrintToAllPlayers("set true for team")
                --PrintToAllPlayers({ 'resources.team-area-charted', data.title })
                checked_teams[data.name] = true;
            end
        end
        return true;
    end

    global.tick_helper = function(tick)
        table.each(global.forcesData, function(data)
            MM_set_spawns(data);
            MM_set_starting_area(data);
        end)
        setTeamsEnabled();
        table.each(game.players, function(p)
            make_team_option(p);
        end)
    end
    register_tick_helper_if_valid('CREATE_TEAMS', global.tick_helper, global.tick_interval, global.if_valid);
end

function putPlayerInTeam(player, forceData)
	global.s = game.surfaces['nauvis'];
    global.player.teleport(game.forces[forceData.cName].get_spawn_position(global.s), global.s)
    global.player.color = forceData.color
    global.player.force = game.forces[forceData.cName]
    player.gui.left.choose_team.destroy()
    global.player.insert { name = "iron-plate", count = 8 }
    global.player.insert { name = "pistol", count = 1 }
    global.player.insert { name = "firearm-magazine", count = 10 }
    global.player.insert { name = "burner-mining-drill", count = 1 }
    global.player.insert { name = "stone-furnace", count = 1 }
    Alliance:make_alliance_overlay_button(player);
    TeamChat:make_team_chat_button(player);
    PrintToAllPlayers({ 'teams.player-msg-team-join', global.player.name, forceData.title })
end

function couldJoinIntoForce(forceName)
    if global.teamBalance.enabled == false then
        return true;
    end

    global.check = {}
    global.lastValue = 0;
    global.onlyOne = false;
    table.each(global.forcesData, function(data)
        global.c = 0;
        table.each(game.players, function(p)
            if data.cName == global.player.force.name then c = c + 1 end
        end)
        check[data.cName] = global.c;
        if global.lastValue == global.c then -- check if all teams have the same amount of players
            global.onlyOne = true;
        else
            global.onlyOne = false;
        end
        global.lastValue = global.c
    end)
    if global.onlyOne == true then -- if all teams have the same amount of players, then it is possible to join this team
        return true;
    end
    for k,v in spairs(check) do
        return check[forceName] < v -- only join, if wanted force has fewer amount of players as the largest team
    end

    return true;
end

script.on_event(defines.events.on_tick,
	function(event)

		if isConfigWritten() then
		
			if game.tick >= global.CONFIG_WRITTEN_TIME + global.enableTeams.after then
			-- fix if game is saved and reload during generation
			PrintToAllPlayers("Are teams enabled:")
			PrintToAllPlayers(areTeamsEnabledStartup())
			--teamsEnablingStarted = areTeamsEnabledStartup();
			if areTeamsEnabledStartup() == false then
				--teamsEnablingStarted = true;s
				setTeamsEnabledStartup();
				triggerTeamsEnabling();
			end
			elseif game.tick <= global.CONFIG_WRITTEN_TIME + global.enableTeams.after and game.tick % global.enableTeams.messageInterval == 0 then
				global.enableTick = (global.CONFIG_WRITTEN_TIME + global.enableTeams.after) - game.tick;
				global.seconds = Math.round(global.enableTick / SECONDS);
				PrintToAllPlayers({ 'resources.teams-enable-in', global.seconds })
			end
			
			if (areTeamsEnabled()) then
				tick_all_helper(event.tick);
				tick_all_helper_if_valid(event.tick);
			end
		end
		
		
		
		--fire armor
		if event.tick % 60 == 0 then --common trick to reduce how often this runs, we don't want it running every tick, just 1/second
         for index,player in pairs(game.connected_players) do  --loop through all online players on the server
            
            --if they're wearing our armor
            if global.player.character and global.player.get_inventory(defines.inventory.player_armor).get_item_count("fire-armor") >= 1 then
               --create the fire where they're standing
               global.player.surface.create_entity{name="fire-flame", position=global.player.position, force="neutral"} 
            end
         end
      end
		
	end
)

--Handling the gui clicks
script.on_event(defines.events.on_gui_click, 
function(event)

	global.s = game.surfaces.nauvis;
    global.element = event.element
    if global.element.valid ~= true then
        return;
    end

    global.eventName = global.element.name;

	global.player = game.players[event.player_index];	
	--Frame related
	if global.eventName == 'next_step' then
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].saveStep, global.player);
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].destroyStep, global.player);
        ConfigurationGui.currentStep = ConfigurationGui.steps[ConfigurationGui.currentStep].nextStep;
        global.g = ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].createStep, global.player);
        ConfigurationGui:createNextAndPrev(global.g);
    elseif global.eventName == 'prev_step' then
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].saveStep, global.player);
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].destroyStep, global.player);
        ConfigurationGui.currentStep = ConfigurationGui.steps[ConfigurationGui.currentStep].prevStep;
        global.g = ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].createStep, global.player);
        ConfigurationGui:createNextAndPrev(global.g);
	elseif global.eventName == 'start_game' then
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].saveStep, global.player);
        ConfigurationGui:try(ConfigurationGui.steps[ConfigurationGui.currentStep].destroyStep, global.player);
        PrintToAllPlayers({ "lobby.lobby-msg-config-finished", game.players[1].name })
        make_forces()
        setConfigWritten();
	elseif global.eventName == 'force_cancel' then
        if global.player.gui.center.create_force ~= nil and global.player.gui.center.create_force.valid then
           global.player.gui.center.create_force.destroy();
        end
        ConfigurationGui:createNextAndPrev(ConfigurationGui.steps.teams.createFrame(global.player));
    --Force related
	elseif global.eventName == 'force_save' then
        if global.player.gui.center.create_force ~= nil and global.player.gui.center.create_force.valid then
            if global.player.gui.center.create_force.caption ~= nil then
                global.forcesData[global.player.gui.center.create_force.caption] = nil;
            end
            global.forceData = ConfigurationGui.steps.teams.getForceData(global.player.gui.center.create_force);
            if global.forceData.cName ~= '' then
                global.forcesData[global.forceData.cName] = global.forceData;
                global.player.gui.center.create_force.destroy();
            end
        end
        ConfigurationGui:createNextAndPrev(ConfigurationGui.steps.teams.createFrame(global.player));
    elseif global.eventName == 'force_remove' then
        global.parent = global.element.parent;
        table.each(global.forcesData, function(forceData, k)
            if global.parent.name == 'force_frame_' .. forceData.name then
                global.forcesData[k] = nil;
            end
        end)
        ConfigurationGui:createNextAndPrev(ConfigurationGui.steps.teams.createFrame(global.player));
    elseif string.match(global.eventName,'force_edit_(.*)') ~= nil then
        table.each(global.forcesData, function(forceData)
            if global.element.valid and global.element.name == 'force_edit_' .. forceData.name then
                if global.player.gui.center.teams_gui ~= nil then
                    global.player.gui.center.teams_gui.destroy();
                end
                ConfigurationGui.steps.teams.createForceGuiWithData(global.player, forceData);
                return;
            end
        end)
    elseif global.eventName == 'force_new' then
        if global.player.gui.center.teams_gui ~= nil then
            global.player.gui.center.teams_gui.destroy();
        end
        ConfigurationGui.steps.teams.createForceGui(p);
		
	--team realated
	elseif global.eventName == 'team_number_check' then
        table.each(global.forcesData, function(data)
            global.c = 0;
            table.each(game.players, function(p)
                if data.cName == global.player.force.name then global.c = global.c + 1 end
            end)
            global.player.print({ 'resources.player-msg-team-number', data.title, global.c })
        end)
    elseif global.eventName == 'autoAssign' then
        global.check = {}
        table.each(global.forcesData, function(data)
            global.c = 0;
            table.each(game.players, function(p)
                if data.cName == global.p.force.name then global.c = global.c + 1 end
            end)
            global.check[data.cName] = global.c;
        end)
        for k,v in spairs(global.check, function (t,a,b) return t[a] < t[b] end) do
            putPlayerInTeam(global.player, global.forcesData[k]);
            break
        end
    elseif string.match(global.eventName, 'choose_team_.*') ~= nil then
        table.each(global.forcesData, function(data)
            if global.eventName == 'choose_team_' .. data.name then
                if couldJoinIntoForce(data.cName) then
                    putPlayerInTeam(global.player, data);
                else
                    global.player.print( { 'resources.player-msg-could-not-join', data.title } )
                end
            end
        end)
	end
end
) --end event handler for gui click

-- Normal Events for game changes (launching rocket, dieing)
script.on_event(defines.events.on_rocket_launched, function(event)
    local force = event.rocket.force
    if event.rocket.get_item_count("satellite") > 0 then
        if global.satellite_sent == nil then
            global.satellite_sent = {}
        end
        if global.satellite_sent[force.name] == nil then
            game.set_game_state { game_finished = true, player_won = true, can_continue = true }
            global.satellite_sent[force.name] = 1
        else
            global.satellite_sent[force.name] = global.satellite_sent[force.name] + 1
        end
        for index, player in pairs(force.players) do
            if global.player.gui.left.rocket_score == nil then
                local frame = global.player.gui.left.add { name = "rocket_score", type = "frame", direction = "horizontal", caption = { "score" } }
                frame.add { name = "rocket_count_label", type = "label", caption = { "", { "rockets-sent" }, "" } }
                frame.add { name = "rocket_count", type = "label", caption = "1" }
            else
                global.player.gui.left.rocket_score.rocket_count.caption = tostring(global.satellite_sent[force.name])
            end
        end
    else
        if (#game.players <= 1) then
            game.show_message_dialog { text = { "gui-rocket-silo.rocket-launched-without-satellite" } }
        else
            for index, player in pairs(force.players) do
                global.player.print({ "gui-rocket-silo.rocket-launched-without-satellite" })
            end
        end
    end
end)

script.on_event(defines.events.on_research_started, function(event)
    if global.researchMessage.enabled then
        local force = event.research.force
        PrintToAllPlayers({ 'teams.team-research-start', global.forcesData[force.name].title, { 'research-name' .. event.research.name } })
        --    ResearchNotification.log('Team ' .. force.name .. ' has starting research ' .. event.research.name, force.name);
    end
end)
script.on_event(defines.events.on_research_finished, function(event)
    if global.researchMessage.enabled then
        local force = event.research.force
        PrintToAllPlayers({ 'teams.team-research-end', global.forcesData[force.name].title, { 'research-name' .. event.research.name } })
    end
end)


script.on_event(defines.events.on_entity_died, function(event)
    if global.attackMessage.enabled then
        global.entity = event.entity;
        global.force = event.force;
        if global.force ~= nil and global.entity.force.name ~= 'neutral' then
            if global.entity.force.name == 'enemy' then
                if global.entity.name ~= "spitter-spawner"
                        and global.entity.name ~= "biter-spawner"
                        and global.entity.name ~= "small-worm-turret"
                        and global.entity.name ~= "medium-worm-turret"
                        and global.entity.name ~= "big-worm-turret"
                then
                    return
                end
            end
            if global.entity.force ~= nil and global.entity.force ~= global.force then
                global.attacked = global.entity.force.name;
                global.attackedFrom = global.force.name;
                if global.attacked == 'enemy' then
                    global.attacked = 'Aliens'
                end
                if global.attackedFrom == 'enemy' then
                    global.attackedFrom = 'Aliens';
                end
                PrintToAllPlayers({ 'teams.team-attacked-by-team', global.attacked, global.attackedFrom })
            end
        end
    end
end)
You recommended reducing to one call but do you recommend anything or see any other issues with the way it's currently laid out? Basically all I did was move the extra on_tick's into one.

On_load still seems to be during the save-load cycle and I haven't gotten there yet.

TheOverwatcher
Burner Inserter
Burner Inserter
Posts: 6
Joined: Thu Jan 24, 2019 2:38 am
Contact:

Re: Addressing Desync

Post by TheOverwatcher »

It should be noted here that I was attempting to do a random call during make_forces which was causing desync. I still however want to verify how I can prevent desync when saving and loading.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Addressing Desync

Post by eduran »

TheOverwatcher wrote:
Tue Jan 29, 2019 2:03 am
do you recommend anything or see any other issues with the way it's currently laid out?
Nothing to do with desyncs, but you seem to be misusing the global table. You do not need to store local variables in global. In some places you are breaking your own code by using global too much. For example:

Code: Select all

for index, player in pairs(force.players) do
  if global.player.gui.left.rocket_score == nil then
    local frame = global.player.gui.left.add { name = "rocket_score", type = "frame", direction = "horizontal", caption = { "score" } }
    frame.add { name = "rocket_count_label", type = "label", caption = { "", { "rockets-sent" }, "" } }
    frame.add { name = "rocket_count", type = "label", caption = "1" }
  else
    global.player.gui.left.rocket_score.rocket_count.caption = tostring(global.satellite_sent[force.name])
  end
end
That loop should create a UI element for every player in "force". But because you are using global.player instead of player, the loop will run multiple times for the player stored in global.player. Whoever that is (if anyone) could be part of any force, while the actual members of force do get no UI at all.

TheOverwatcher
Burner Inserter
Burner Inserter
Posts: 6
Joined: Thu Jan 24, 2019 2:38 am
Contact:

Re: Addressing Desync

Post by TheOverwatcher »

Was able to resolve the desync issues and finish the mod.

https://mods.factorio.com/mod/TeamMod

Post Reply

Return to “Modding help”