For the two random number functions in the title (TerrainBuilder.GetRandomNumber vs Game.GetRandNum) is one better to use than the other? Especially when considering multiplayer desync issues?
I made a mod that makes so unique units, improvements, and buildings are unlocked randomly. Currently I am using TerrainBuilder.GetRandomNumber based on an old thread I saw on here, but when playing with friends one of the players always seem to have a lot of sync issues. A little more detail - did a four player game and player R kept having sync issues, but all other players were fine including me and players B & C. Also tried a three player game without R, but instead player B had all the sync issues. If we do a game without the mod then sync issues go away (well are much more rare).
The random function can be called due to Events.ResearchCompleted or Events.CivicCompleted. Also at the beginning of the game a check is performed on uniques that don't have any prereq tech or civic, and this is called based on GameEvents.OnGameTurnStarted. Could using random numbers during any of these events cause desync issues?
Only other thing I can think of that might cause issues is if for loops iterate in a different order for different players. My for loops iterate over GameInfo like the line below, or use ipairs which from what I understand is better to use than pairs.
for row in GameInfo.Units() do
Here is the full LUA portion of the code. There are some parts I could simplify - at first I thought it would help if I stored some data in tables, but then ended up only using them to retrieve names.
I made a mod that makes so unique units, improvements, and buildings are unlocked randomly. Currently I am using TerrainBuilder.GetRandomNumber based on an old thread I saw on here, but when playing with friends one of the players always seem to have a lot of sync issues. A little more detail - did a four player game and player R kept having sync issues, but all other players were fine including me and players B & C. Also tried a three player game without R, but instead player B had all the sync issues. If we do a game without the mod then sync issues go away (well are much more rare).
The random function can be called due to Events.ResearchCompleted or Events.CivicCompleted. Also at the beginning of the game a check is performed on uniques that don't have any prereq tech or civic, and this is called based on GameEvents.OnGameTurnStarted. Could using random numbers during any of these events cause desync issues?
Only other thing I can think of that might cause issues is if for loops iterate in a different order for different players. My for loops iterate over GameInfo like the line below, or use ipairs which from what I understand is better to use than pairs.
for row in GameInfo.Units() do
Here is the full LUA portion of the code. There are some parts I could simplify - at first I thought it would help if I stored some data in tables, but then ended up only using them to retrieve names.
Spoiler :
Code:
-- RandomUnique
-- Author: RedKey
-- DateCreated: 10/4/2020
--------------------------------------------------------------
-- Change these 3 variables to change the chance of uniques unlocking. Represents chance out of 100. e.g. 10 out of 100.
local iUniqueUnitChance : number = 10;
local iUniqueBuildingChance : number = 10;
local iUniqueImprovementChance : number = 10;
--------------------------------------------------------------
local UniqueType = {};
local TechUnlocks = {};
local CivicUnlocks = {};
local UnitData:table = {
Name = {},
Tech = {},
Civic = {},
Replaces = {},
};
local ImprovementData:table = {
Name = {},
Tech = {},
Civic = {},
};
local BuildingData:table = {
Name = {},
Tech = {},
Civic = {},
Replaces = {},
};
-- Store a list of all uniques unlocked by a tech
function CachePrereqTech (sTech, sType)
if (sTech) then
if (TechUnlocks[sTech] == nil) then
TechUnlocks[sTech] = {sType};
else
table.insert(TechUnlocks[sTech],sType);
end
end
end
-- Store a list of all uniques unlocked by a civic
function CachePrereqCivic (sCivic, sType)
if (sCivic) then
if (CivicUnlocks[sCivic] == nil) then
CivicUnlocks[sCivic] = {sType};
else
table.insert(CivicUnlocks[sCivic],sType);
end
end
end
function GetUnitData()
for row in GameInfo.Units() do
if (row.TraitType == 'RU_TRAIT_CIVILIZATION_NO_PLAYER') then
local unitType = row.UnitType;
UnitData.Name[unitType] = Locale.Lookup(row.Name);
UnitData.Tech[unitType] = row.PrereqTech;
UnitData.Civic[unitType] = row.PrereqCivic;
UniqueType[unitType] = 'UNIT';
CachePrereqTech(row.PrereqTech, unitType);
CachePrereqCivic(row.PrereqCivic, unitType);
end
end
for row in GameInfo.UnitReplaces() do
if (UnitData.Name[row.CivUniqueUnitType]) then
UnitData.Replaces[row.CivUniqueUnitType] = row.ReplacesUnitType;
end
end
end
function GetImprovementData()
for row in GameInfo.Improvements() do
if (row.TraitType == 'RU_TRAIT_CIVILIZATION_NO_PLAYER') then
local impType = row.ImprovementType;
ImprovementData.Name[impType] = Locale.Lookup(row.Name);
ImprovementData.Tech[impType] = row.PrereqTech;
ImprovementData.Civic[impType] = row.PrereqCivic;
UniqueType[impType] = 'IMPROVEMENT';
CachePrereqTech(row.PrereqTech, impType);
CachePrereqCivic(row.PrereqCivic, impType);
end
end
end
function GetBuildingData()
for row in GameInfo.Buildings() do
if (row.TraitType == 'RU_TRAIT_CIVILIZATION_NO_PLAYER') then
local buildingType = row.BuildingType;
BuildingData.Name[buildingType] = Locale.Lookup(row.Name);
BuildingData.Tech[buildingType] = row.PrereqTech;
BuildingData.Civic[buildingType] = row.PrereqCivic;
UniqueType[buildingType] = 'BUILDING';
CachePrereqTech(row.PrereqTech, buildingType);
CachePrereqCivic(row.PrereqCivic, buildingType);
end
end
for row in GameInfo.BuildingReplaces() do
if (BuildingData.Name[row.CivUniqueBuildingType]) then
BuildingData.Replaces[row.CivUniqueBuildingType] = row.ReplacesBuildingType;
end
end
end
function DoRandomUnlock (iChance)
return (TerrainBuilder.GetRandomNumber(100, "Random Unique Unlock") < iChance);
end
function TryUnlockUnit (pPlayer, unitType)
if (DoRandomUnlock(iUniqueUnitChance)) then
pPlayer:AttachModifierByID('RU_UNLOCK_' .. unitType);
-- if (UnitData.Replaces[unitType]) then
-- pPlayer:AttachModifierByID('RU_DISABLE_' .. UnitData.Replaces[unitType]);
-- end
local unitName = UnitData.Name[unitType];
NotificationManager.SendNotification(pPlayer:GetID(), NotificationTypes.USER_DEFINED_1, unitName .. ' Unlocked', 'Your people have developed a unique unit - the ' .. unitName);
end
end
function TryUnlockImprovement (pPlayer, improvementType)
if (DoRandomUnlock(iUniqueImprovementChance)) then
pPlayer:AttachModifierByID('RU_UNLOCK_' .. improvementType);
local impName = ImprovementData.Name[improvementType];
NotificationManager.SendNotification(pPlayer:GetID(), NotificationTypes.USER_DEFINED_2, impName .. ' Unlocked', 'Your people have developed a unique improvement - the ' .. impName);
end
end
function TryUnlockBuilding (pPlayer, buildingType)
if (DoRandomUnlock(iUniqueBuildingChance)) then
pPlayer:AttachModifierByID('RU_UNLOCK_' .. buildingType);
local buildingName = BuildingData.Name[buildingType];
NotificationManager.SendNotification(pPlayer:GetID(), NotificationTypes.USER_DEFINED_3, buildingName .. ' Unlocked', 'Your people have developed a unique building - the ' .. buildingName);
end
end
function UnlockUniques (iTurn)
if (iTurn == 1) then
UnlockInitialUniques();
end
end
GameEvents.OnGameTurnStarted.Add(UnlockUniques);
-- Unlocks uniques with no prereq techs or civics
function UnlockInitialUniques ()
for _, playerID in ipairs(PlayerManager.GetAliveMajorIDs()) do
local pPlayer:table = Players[playerID];
print("Unlocking initial uniques for player" .. playerID);
for row in GameInfo.Units() do
if (row.TraitType == "RU_TRAIT_CIVILIZATION_NO_PLAYER") then
local unitType = row.UnitType;
if (row.PrereqTech == nil and row.PrereqCivic == nil) then
TryUnlockUnit(pPlayer, unitType);
end
end
end
for row in GameInfo.Improvements() do
if (row.TraitType == "RU_TRAIT_CIVILIZATION_NO_PLAYER") then
local impType = row.ImprovementType;
if (row.PrereqTech == nil and row.PrereqCivic == nil) then
TryUnlockImprovement(pPlayer, impType);
end
end
end
for row in GameInfo.Buildings() do
if (row.TraitType == "RU_TRAIT_CIVILIZATION_NO_PLAYER") then
local buildType = row.BuildingType;
if (row.PrereqTech == nil and row.PrereqCivic == nil) then
TryUnlockBuilding(pPlayer, buildType);
end
end
end
end
end
-- Unlocks uniques with a prereq tech
function UnlockUniqueFromTech (playerID, iTech)
local pPlayer = Players[playerID]
local sTechType = GameInfo.Technologies[iTech].TechnologyType
if (TechUnlocks[sTechType] ~= nil) then
for i,unlock in ipairs(TechUnlocks[sTechType]) do
if (UniqueType[unlock] == 'UNIT') then
TryUnlockUnit(pPlayer, unlock)
elseif (UniqueType[unlock] == 'IMPROVEMENT') then
TryUnlockImprovement(pPlayer, unlock)
elseif (UniqueType[unlock] == 'BUILDING') then
TryUnlockBuilding(pPlayer, unlock)
end
end
end
end
Events.ResearchCompleted.Add(UnlockUniqueFromTech)
-- Unlocks uniques with a prereq civic
function UnlockUniqueFromCivic (playerID, iCivic, bCancelled)
if (bCancelled) then return end
local pPlayer = Players[playerID]
local sCivicType = GameInfo.Civics[iCivic].CivicType
if (CivicUnlocks[sCivicType] ~= nil) then
for i,unlock in ipairs(CivicUnlocks[sCivicType]) do
if (UniqueType[unlock] == 'UNIT') then
TryUnlockUnit(pPlayer, unlock)
elseif (UniqueType[unlock] == 'IMPROVEMENT') then
TryUnlockImprovement(pPlayer, unlock)
elseif (UniqueType[unlock] == 'BUILDING') then
TryUnlockBuilding(pPlayer, unlock)
end
end
end
end
Events.CivicCompleted.Add(UnlockUniqueFromCivic)
function Initialize()
GetUnitData();
GetImprovementData();
GetBuildingData();
end
Initialize();