Jump to content

Dynamic merge function for map distributions


stuck1a

Recommended Posts

  

Hi there,

 

I'm currently shorten some scripts for my server project but struggling with the following:

Instead of an endless procedural-style function for the MapDistributions like this:

local function preDistributionMerge()
  -- MyFancyMap1
  table.insert(ProceduralDistributions.list.CrateMaps.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.CrateMaps.items, 50)
  table.insert(ProceduralDistributions.list.CrateMaps.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.CrateMaps.items, 20)
  table.insert(ProceduralDistributions.list.CrateMechanics.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.CrateMechanics.items, 2)
  table.insert(ProceduralDistributions.list.GasStorageMechanics.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.GasStorageMechanics.items, 2)
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, 20)
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, 10)
  table.insert(ProceduralDistributions.list.StoreShelfMechanics.items, "MyFancyMap1")
  table.insert(ProceduralDistributions.list.StoreShelfMechanics.items, 2)
  -- MyFancyMap2
  table.insert(ProceduralDistributions.list.CrateMaps.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.CrateMaps.items, 50)
  table.insert(ProceduralDistributions.list.CrateMaps.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.CrateMaps.items, 20)
  table.insert(ProceduralDistributions.list.CrateMechanics.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.CrateMechanics.items, 2)
  table.insert(ProceduralDistributions.list.GasStorageMechanics.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.GasStorageMechanics.items, 2)
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, 20)
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.MagazineRackMaps.items, 10)
  table.insert(ProceduralDistributions.list.StoreShelfMechanics.items, "MyFancyMap2")
  table.insert(ProceduralDistributions.list.StoreShelfMechanics.items, 2)
  -- ...
end
Events.OnPreDistributionMerge.Add(preDistributionMerge)

 

 

I wanted to shorten things a bit and tried to replace the above code (which works without any issues) with the following one:

(could be shortened even more of course)

local function preDistributionMerge()
  local MapItemList = {
    "MyFancyMap1",
    "MyFancyMap2",
    -- ...
  }
  local function ArrIter(t)
    local i = 0
    local n = table.getn(t)
    return function ()
      i = i + 1
      if i <= n then return t[i] end
    end
  end
  for x in ArrIter(MapItemList) do
    table.insert(ProceduralDistributions.list.CrateMaps.items, x)
    table.insert(ProceduralDistributions.list.CrateMaps.items, 50)
    table.insert(ProceduralDistributions.list.CrateMaps.items, x)
    table.insert(ProceduralDistributions.list.CrateMaps.items, 20)
    table.insert(ProceduralDistributions.list.CrateMechanics.items, x)
    table.insert(ProceduralDistributions.list.CrateMechanics.items, 2)
    table.insert(ProceduralDistributions.list.GasStorageMechanics.items, x)
    table.insert(ProceduralDistributions.list.GasStorageMechanics.items, 2)
    table.insert(ProceduralDistributions.list.MagazineRackMaps.items, x)
    table.insert(ProceduralDistributions.list.MagazineRackMaps.items, 20)
    table.insert(ProceduralDistributions.list.MagazineRackMaps.items, x)
    table.insert(ProceduralDistributions.list.MagazineRackMaps.items, 10)
    table.insert(ProceduralDistributions.list.StoreShelfMechanics.items, x)
    table.insert(ProceduralDistributions.list.StoreShelfMechanics.items, 2)
  end
end
Events.OnPreDistributionMerge.Add(preDistributionMerge)

 

But in fact, with the second version, none of the given MapItems will appear as loot.

Shouldn't this be equivalent or does I miss something?

Since I'm new to Lua scripting, I'm guessing it's the latter lol

 

 

Best regards,

stuck1a

 

Link to comment
Share on other sites

Hi, have you confirmed that the new data has been properly inserted? Did you tested the second code on a new world? Or, tried using the first code again and see if there's any difference? The code looks fine to me. So, if you've already confirmed that the new data has been properly inserted or the event didn't call your function, that means it's probably the server loot spawn settings. It didn't generate new loots after the world was created.

 

And I would like to suggest you to switch iterator with indexed for loop. It's much better for your case since you don't really need to declare the iterator function.

 

Here's the clean version you can use for your server. More like anyone are welcome to use it.

local MapItemList = {
	"MyFancyMap1",
	"MyFancyMap2",
	-- ...
}

-- edited: was looking at proceduraldistributions.lua for loot table names, looks like this one is clear to add
local function getLootTable(strLootTableName)
	return ProceduralDistributions.list[strLootTableName]
end

--[[ haven't try variable argument yet so it might not work
-- insertItem(tLootTable, strItem, weights separated by comma)
-- insertItem(tLootTable, strItem, 50, 20)
local function insertItem(tLootTable, strItem, ...)
	for i, iWeight in ipairs(arg) do
		table.insert(tLootTable.items, strItem)
		table.insert(tLootTable.items, iWeight)
	end
end
--]]

local function insertItem(tLootTable, strItem, iWeight)
	table.insert(tLootTable.items, strItem)
	table.insert(tLootTable.items, iWeight)
end

local function preDistributionMerge()
	for i=1, #MapItemList do
		local strItem = MapItemList[i]
		
		-- CrateMaps Room
		local tLootTable = getLootTable("CrateMaps")
		insertItem(tLootTable, strItem, 50)
		insertItem(tLootTable, strItem, 20)
		-- variable version
		--insertItem(tLootTable, strItem, 50, 20)
		
		tLootTable = getLootTable("CrateMechanics")
		insertItem(tLootTable, strItem, 2)
		
		tLootTable = getLootTable("GasStorageMechanics")
		insertItem(tLootTable, strItem, 2)
		
		tLootTable = getLootTable("MagazineRackMaps")
		insertItem(tLootTable, strItem, 20)
		insertItem(tLootTable, strItem, 10)
		--insertItem(tLootTable, strItem, 20, 10)
		
		tLootTable = getLootTable("StoreShelfMechanics")
		insertItem(tLootTable, strItem, 2)
	end
end
Events.OnPreDistributionMerge.Add(preDistributionMerge)

 

Edited by Zeros
improvise the code some more
Link to comment
Share on other sites

 

37 minutes ago, Zeros said:

Hi, have you confirmed that the new data has been properly inserted? Did you tested the second code on a new world? Or, tried using the first code again and see if there's any difference?

Well, I've wiped the server just the way I usually do:

- Delete file .cache/db/<Servername>.db

- Delete folder .cache/Saves/Multiplayer/<Servername>

 

Sometimes I also change the ResetID of the server config file or simply delete the Zomboid/Saves/Multiplayer/<IP <UID> folder client side as well (guess it doesn't matter)

 

 

37 minutes ago, Zeros said:

And I would like to suggest you to switch iterator with indexed for loop. It's much better for your case since you don't really need to declare the iterator function.

I agree with you. To be honest, the ArrayIter definition is a relict from debugging I've forgot. Thanks for the hint.

 

 

 

37 minutes ago, Zeros said:

 Here's the clean version you can use for your server. More like anyone are welcome to use it.

local MapItemList = {
	"MyFancyMap1",
	"MyFancyMap2",
	-- ...
}

local function insertItem(tLootTable, strItem, iWeight)
	table.insert(tLootTable.items, strItem)
	table.insert(tLootTable.items, iWeight)
end

local function preDistributionMerge()
	for i=1, #MapItemList do
		local strItem = MapItemList[i]
		
		-- CrateMaps Room
		local tLootTable = ProceduralDistributions.list.CrateMaps
		insertItem(tLootTable, strItem, 50)
		insertItem(tLootTable, strItem, 20)
		
		tLootTable = ProceduralDistributions.list.CrateMechanics
		insertItem(tLootTable, strItem, 2)
		
		tLootTable = ProceduralDistributions.list.GasStorageMechanics
		insertItem(tLootTable, strItem, 2)
		
		tLootTable = ProceduralDistributions.list.MagazineRackMaps
		insertItem(tLootTable, strItem, 20)
		insertItem(tLootTable, strItem, 10)
		
		tLootTable = ProceduralDistributions.list.StoreShelfMechanics
		insertItem(tLootTable, strItem, 2)
	end
end
Events.OnPreDistributionMerge.Add(preDistributionMerge)

Thanks for that. I'll give it a try after work.

 

 

Regards,

stuck

 

Edited by stuck1a
Link to comment
Share on other sites

I had a look at the proceduraldistributions.lua and it seems like there should really be no problem with your second code. Just need to find out if the function is actually being called.

 

The code has been edited with further improvements. Not sure if the variable argument version will work since I've not tried it yet.

 

1 hour ago, Zeros said:
local MapItemList = {
	"MyFancyMap1",
	"MyFancyMap2",
	-- ...
}

-- edited: was looking at proceduraldistributions.lua for loot table names, looks like this one is clear to add
local function getLootTable(strLootTableName)
	return ProceduralDistributions.list[strLootTableName]
end

--[[ haven't try variable argument yet so it might not work
-- insertItem(tLootTable, strItem, weights separated by comma)
-- insertItem(tLootTable, strItem, 50, 20)
local function insertItem(tLootTable, strItem, ...)
	for i, iWeight in ipairs(arg) do
		table.insert(tLootTable.items, strItem)
		table.insert(tLootTable.items, iWeight)
	end
end
--]]

local function insertItem(tLootTable, strItem, iWeight)
	table.insert(tLootTable.items, strItem)
	table.insert(tLootTable.items, iWeight)
end

local function preDistributionMerge()
	for i=1, #MapItemList do
		local strItem = MapItemList[i]
		
		-- CrateMaps Room
		local tLootTable = getLootTable("CrateMaps")
		insertItem(tLootTable, strItem, 50)
		insertItem(tLootTable, strItem, 20)
		-- variable version
		--insertItem(tLootTable, strItem, 50, 20)
		
		tLootTable = getLootTable("CrateMechanics")
		insertItem(tLootTable, strItem, 2)
		
		tLootTable = getLootTable("GasStorageMechanics")
		insertItem(tLootTable, strItem, 2)
		
		tLootTable = getLootTable("MagazineRackMaps")
		insertItem(tLootTable, strItem, 20)
		insertItem(tLootTable, strItem, 10)
		--insertItem(tLootTable, strItem, 20, 10)
		
		tLootTable = getLootTable("StoreShelfMechanics")
		insertItem(tLootTable, strItem, 2)
	end
end
Events.OnPreDistributionMerge.Add(preDistributionMerge)

 

 

Edited by Zeros
bad syntax highlighting
Link to comment
Share on other sites

9 hours ago, Zeros said:

I had a look at the proceduraldistributions.lua and it seems like there should really be no problem with your second code. Just need to find out if the function is actually being called.

According to the logs, the function was loaded without issues, at least the script file got properly loaded and also no noteable errors were thrown.

Since I haven't found time for it yet, I haven't gotten very familiar with debug mode yet. So I tested the loot distribution by teleporting to some gas stations and checked their magazine racks. Around 10 racks in total. Maybe I just had bad luck, because I only found vanilla cards there.

 

 

9 hours ago, Zeros said:

The code has been edited with further improvements. Not sure if the variable argument version will work since I've not tried it yet.

Looks very similar to my initial version. Since its surely possible to pass function references from strings with enough know how abot the zomboid lua API, I couldn't get this to work without using global objects, when I've tried it for the MapDefinition functions (lua/client/ISUI/Maps/MyFancyMapDefinitions.lua) which lead me to restoring the procedural way there, too.

I'm curious to see if that works, so I have a reference.

After around 20 years in software development, I'm already used to a lack of documentation - nevertheless, I'm unusally struggling with diving into Lua and the PZ Lua Integration. Well, it can only get better.

 

 

 

// Edit:

Based on your source code, below is the solution I originally aimed for the distribution.
Works well in SciTE, let's see if it works in-game as well. I've omitted the varargs part for now.

Maybe I'll extend this later with IO operations to read in all items introduced by a mod and also the vanilla distributions for the corresponding item type, if available.

So I could get rid of adding each distribution manually and just have to add ItemDist entries if something shall differ from vanilla for balancing or if completely new item types are introduced. At least, if its no major performance impact, since there a some more scripts I'd like to add generic creation, Let's see - but for now, I'm fine with this.

local ItemDist = {
  -- LootableMaps
  {
    Distributions = {
      {"CrateMaps", 50},
      {"CrateMaps", 20},
      {"CrateMechanics", 2},
      {"GasStorageMechanics", 2},
      {"MagazineRackMaps", 20},
      {"MagazineRackMaps", 10},
      {"StoreShelfMechanics", 2},
    },
    Items = {
      "MyFancyMap1",
      "MyFancyMap2",
      -- ...
    }
  },
  -- ... (further item types)
}



local function getLootTable(strLootTableName)
  return ProceduralDistributions.list[strLootTableName]
end


local function insertItem(tLootTable, strItem, iWeight)
  table.insert(tLootTable.items, strItem)
  table.insert(tLootTable.items, iWeight)
end


local function preDistributionMerge()
  for i=1, #ItemDist do
    for j=1, #(ItemDist[i].Distributions) do
      for k=1, #(ItemDist[i].Items) do
        local tLootTable = getLootTable(ItemDist[i].Distributions[j][1])
        local strItem = ItemDist[i].Items[k]
        local iWeight = ItemDist[i].Distributions[j][2]
        insertItem(tLootTable, strItem, iWeight)
      end
    end
  end
end

Events.OnPreDistributionMerge.Add(preDistributionMerge)

Again, thanks for your help!

 

// Edit2:

Tested in-game, works as excepted-

Edited by stuck1a
Added routine
Link to comment
Share on other sites

It's good to know that it's working as expected. I was suspecting the rolling magic mentioned by the developers user known as 'TheCommander' is not working that well when the item list is big (20+ items) and the number of rolls is low(4~6 rolls). Hence the reason why you can't find it in the previous test.

 

19 hours ago, stuck1a said:

After around 20 years in software development, I'm already used to a lack of documentation - nevertheless, I'm unusally struggling with diving into Lua and the PZ Lua Integration. Well, it can only get better.

No worries, it will get much better once you get used to it. Or, you can try improve your lua environment by simulating some keywords from other languages which I am doing right now.

 

19 hours ago, stuck1a said:

Based on your source code, below is the solution I originally aimed for the distribution.
Works well in SciTE, let's see if it works in-game as well. I've omitted the varargs part for now.

Maybe I'll extend this later with IO operations to read in all items introduced by a mod and also the vanilla distributions for the corresponding item type, if available

Yeap, that's a good idea.

 

19 hours ago, stuck1a said:

So I could get rid of adding each distribution manually and just have to add ItemDist entries if something shall differ from vanilla for balancing or if completely new item types are introduced. At least, if its no major performance impact, since there a some more scripts I'd like to add generic creation, Let's see - but for now, I'm fine with this.

If you don't mind, I would like to suggest you to take a look at' zombie.inventory.ItemPickerJava' and see if you can simulate the distribution function. The distributions probably can be disabled by assigning empty table to 'ProceduralDsistributions.list' and then use 'OnPostDistributionMerge' event for custom distribution.

 

I haven't check the code in details but based on the server ini, there might be a balance bug for safehouse. A safehouse might be able respawn loots as long no one is constructing anything inside and their item containers have lower amount of items than the respawn threshold. Do pay attention to that possible bug if you're planning to simulate or replicate the function for mod usage.

 

19 hours ago, stuck1a said:
local ItemDist = {
  -- LootableMaps
  {
    Distributions = {
      {"CrateMaps", 50},
      {"CrateMaps", 20},
      {"CrateMechanics", 2},
      {"GasStorageMechanics", 2},
      {"MagazineRackMaps", 20},
      {"MagazineRackMaps", 10},
      {"StoreShelfMechanics", 2},
    },
    Items = {
      "MyFancyMap1",
      "MyFancyMap2",
      -- ...
    }
  },
  -- ... (further item types)
}



local function getLootTable(strLootTableName)
  return ProceduralDistributions.list[strLootTableName]
end


local function insertItem(tLootTable, strItem, iWeight)
  table.insert(tLootTable.items, strItem)
  table.insert(tLootTable.items, iWeight)
end


local function preDistributionMerge()
  for i=1, #ItemDist do
    for j=1, #(ItemDist[i].Distributions) do
      for k=1, #(ItemDist[i].Items) do
        local tLootTable = getLootTable(ItemDist[i].Distributions[j][1])
        local strItem = ItemDist[i].Items[k]
        local iWeight = ItemDist[i].Distributions[j][2]
        insertItem(tLootTable, strItem, iWeight)
      end
    end
  end
end

Events.OnPreDistributionMerge.Add(preDistributionMerge)

Much better now. Do you mind if I use this? I'm planning to work on roguelike mod which focused on short term gameplay once my radar mod is completed and item distributions is in the checklist.

 

And, no problem. Glad that I can help you.

Edited by Zeros
forgive me please, not mentioned by developers but user known as 'TheCommander'
Link to comment
Share on other sites

 

4 hours ago, Zeros said:

No worries, it will get much better once you get used to it. Or, you can try improve your lua environment by simulating some keywords from other languages which I am doing right now.

Yes, did this for some basic stuff, already. But since I want to learn the "lua way" of solving problems, I'll try to keep this at a minimum.

I've been working with Lua for about two weeks now and I think I've gradually understood the basic logic of Lua. Even if it is still a bit unusual that even the most common primitive types are represented as symbols/tables and such things. Feels a bit like back in the late 90's... actually quite fitting for PZ lol

 

 

4 hours ago, Zeros said:

If you don't mind, I would like to suggest you to take a look at' zombie.inventory.ItemPickerJava' and see if you can simulate the distribution function. The distributions probably can be disabled by assigning empty table to 'ProceduralDsistributions.list' and then use 'OnPostDistributionMerge' event for custom distribution.

Didn't get the exact point right now, but I guess you mean implementing a secondary distribution logic on the level of the vanilla one? Well, I'll surely see as soon as I'll take a look into the class. I'll take a look when I get a chance.

 

 

4 hours ago, Zeros said:

I haven't check the code in details but based on the server ini, there might be a balance bug for safehouse. A safehouse might be able respawn loots as long no one is constructing anything inside and their item containers have lower amount of items than the respawn threshold. Do pay attention to that possible bug if you're planning to simulate or replicate the function for mod usage.

I will check that, thanks for the hint. There will surely a possibility to patch this, if necessary. In fact, I'm planning a feature where players can "buy" a custom safe zone in late-game through a NPC shop with a server currency they can earn through missions etc. I'll check that.

 

 

4 hours ago, Zeros said:

I was suspecting the rolling magic mentioned by the developers user known as 'TheCommander' is not working that well when the item list is big (20+ items) and the number of rolls is low(4~6 rolls). Hence the reason why you can't find it in the previous test.

Again, thanks for the hint. Well maybe thats the reason why most map mods don't follow the vanilla map distribution entries for lootable maps, but instead use entries with weights around 20 and 50 only. However, for know, it seems to work like it should, but gonna check this through more detailed testing.

 

Link to comment
Share on other sites

29 minutes ago, stuck1a said:

Didn't get the exact point right now, but I guess you mean implementing a secondary distribution logic on the level of the vanilla one? Well, I'll surely see as soon as I'll take a look into the class. I'll take a look when I get a chance.

Nah, more like replacing the distribution logic with yours. Just in case you need better manipulation on the distribution.

 

1 hour ago, stuck1a said:

Again, thanks for the hint. Well maybe thats the reason why most map mods don't follow the vanilla map distribution entries for lootable maps, but instead use entries with weights around 20 and 50 only. However, for know, it seems to work like it should, but gonna check this through more detailed testing.

I had a look at the source again and the reason why your map items didn't spawn probably because the loot table you modified didn't get selected for loot generation.

Single type of container can contain more than one loottable, and if you didn't force it, it has chance of not getting selected. Pretty sure those mods have the other loottable entry removed or set it's weight lower. Otherwise, there's high chance their items of not being spawned.

 

Here's the problematic part.

-- 'media/lua/server/Items/Distributions.lua
-- Look at the procList
-- Each entry is a loot table to be selected
-- Related functions inside ItemPickerJava: #rollProceduralItem and #getDistribInHashMap
gasstorage = {
  counter = {
    procedural = true,
    procList = {
      {name="JanitorChemicals", min=0, max=99, weightChance=100},
      {name="JanitorCleaning", min=0, max=1, forceForTiles="fixtures_sinks_01_0;fixtures_sinks_01_1;fixtures_sinks_01_2;fixtures_sinks_01_3;fixtures_sinks_01_4;fixtures_sinks_01_5;fixtures_sinks_01_6;fixtures_sinks_01_7;fixtures_sinks_01_8;fixtures_sinks_01_9;fixtures_sinks_01_10;fixtures_sinks_01_11;fixtures_sinks_01_16;fixtures_sinks_01_17;fixtures_sinks_01_18;fixtures_sinks_01_19"},
      {name="JanitorMisc", min=1, max=1, weightChance=100},
      {name="JanitorTools", min=0, max=1, weightChance=100},
    }
  },
  crate = {
    procedural = true,
    procList = {
      {name="GasStorageMechanics", min=1, max=99, weightChance=100},
      {name="GasStorageCombo", min=0, max=99, weightChance=80},
    }
  },
  metal_shelves = {
    procedural = true,
    procList = {
      {name="GasStorageMechanics", min=1, max=99, weightChance=100},
      {name="GasStorageCombo", min=0, max=99, weightChance=80},
    }
  }
}

 

Hope this new information helps with your next distribution test.

Link to comment
Share on other sites

7 hours ago, Zeros said:

IDo you mind if I use this?

Serve yourself as you please. For any source I've created (outside of working hours) generally WTF applies, i.e. do what the fuck you want with it (eat it, marry it, sell it to Russia or just use it) :-)

 

7 hours ago, Zeros said:

Nah, more like replacing the distribution logic with yours. Just in case you need better manipulation on the distribution

Ah, now I get it. Well, let's so how good it works and if bad, how good it can be leveraged without forcing to adapt any vanilla changes after game updates manually.

Guess I should really take some time to take a closer look at the Java classes (I'll just never understand how a game developer can come to the conclusion that Java is the best choice for their project - even if it would an text adventure lol Except  you want to play your game on an ATM or whatever embedded system, since porting to consoles has recently become similarly comfortable for most modern languages.. however)

 

 

7 hours ago, Zeros said:

Here's the problematic part.

Mh, I see what you mean. Well, in fact this could become problematic depending on the context. There should be some quck and dirty possibilties to fix, but I would also prefer a neat solution as long as the wheel doesn't have to be reinvented.

Edited by stuck1a
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...