Pimp my Chests

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
Post Reply
User avatar
Xardas
Posts: 83
Joined: Fri Jul 28, 2017 12:30 am

Pimp my Chests

Post by Xardas » Sun May 06, 2018 8:18 pm

Hey guys,
I was thinking about a script, which always spawns different loot in your chests, so everytime you would start a new game you will find different items, so you have a different gaming experience every time you start a new game. This is what i came up with.

Chests:
To make this work, you must have the dm_ressource pack included to your init.lua file.
Here´s a link to it:
https://www.nexusmods.com/legendofgrimrock2/mods/48/

Code: Select all

	loot  		= {}
	data		= {amount,maxamount,reroll}
	taboo		= {}

function init()
--[[style  ]]	loot[1]		= {"dagger","dm_pod_dust","rock","dm_bone","blowpipe","dm_ashes","dm_screws","dm_shredded_rags","horn","island_map","scope","skull","vilson_orb","dm_weapon_eye_of_time","dm_weapon_stormring","flute","timepiece","sling","compass","mortar","dm_flask_water","dm_flask_empty","flask"}
--[[food   ]]	loot[2]		= {"baked_maggot","blueberry_pie","boiled_beetle","borra","bread","cheese","dm_food_bread","dm_food_cheese","dm_food_cheese_fresh","dm_food_corn","dm_food_mushroom_slice","fjeld_warg_meat","herder_cap","horned_fruit","ice_lizard_steak","lizard_stick","mole_jerky","pitroot_bread","rat_shank","rat_swarm_shank","rotten_pitroot_bread","sausage","silver_roach","smoked_bass","snail_slice","snake_tail","turtle_eggs","turtle_steak","warg_meat"}
--[[herbs  ]]	loot[3]		= {"blackmoss","blooddrop_cap","crystal_flower","etherweed","falconskyre","mudwort"}
--[[potions]]	loot[4]		= {"potion_bear_form","potion_cure_disease","potion_cure_poison","potion_dexterity","potion_energy","potion_greater_energy","potion_greater_healing","potion_healing","potion_poison","potion_rage","potion_resurrection","potion_shield","potion_speed"}
--[[coins  ]]	loot[5]		= {"dm_coin_copper","dm_coin_silver","dm_coin_gold"}
--[[bombs  ]]	loot[6]		= {"dm_bomb_ful","dm_bomb_ven","fire_bomb","frost_bomb","poison_bomb","shock_bomb"}
--[[stack. ]]	loot[7]		= {"pellet_box","throwing_knife","shuriken","cannon_ball","dm_weapon_dart","dm_weapon_dart_poisoned","dart","sleep_dart"}
--[[scrolls]]	loot[8]		= {"scroll_darkbolt","scroll_darkness","scroll_dispel","scroll_fire_shield","scroll_fireball","scroll_fireburst","scroll_force_field","scroll_frost_shield","scroll_frostbolt","scroll_ice_shards","scroll_invisibility","scroll_light","scroll_lightning_bolt","scroll_meteor_storm","scroll_poison_bolt","scroll_poison_cloud","scroll_poison_shield","scroll_shield","scroll_shock","scroll_shock_shield"}
--[[amulets]]	loot[9]		= {"ring_string","dm_necklace_choker","dm_necklace_ekkhard_cross","dm_necklace_feral_pendant","dm_necklace_gem_ages","dm_necklace_hellion","dm_necklace_illumulet","dm_necklace_moonstone","dm_necklace_symal","frostbite_necklace","gear_necklace","runestone_necklace","storm_amulet","bone_amulet","crystal_amulet","nergal_amulet","neck_chain","jewel_pendant","spirit_mirror_pendant","spiritwalker_pendant"}
--[[bracers]]	loot[10]	= {"brace_fortitude","bracelet_tirin","bronze_brace","hardstone_bracelet","leafbond_bracelet","serpent_bracer","steel_armband","dm_rabbits_foot"}
--[[shards ]]	loot[11]	= {"crystal_shard_healing","crystal_shard_protection","crystal_shard_recharging"}
--[[perma. ]]	loot[12]	= {"potion_strength","potion_vitality","potion_willpower","tome_air","tome_earth","tome_energy","tome_fire","tome_health","tome_leadership","tome_moon","tome_water","tome_wisdom"}
	
	data.amount		= 0 --current chest
	data.maxamount	= 1 --put your highest chestnumber here
	data.reroll		= 1 --for spawning multiple times
end

delayedCall(self.go.id,0.01,'init')
	
function pimpmychests()
	local lengthNum  = 0 --length of loot 
	local lengthNum2 = 0 --length of loot[lengthNum]
	
	local chest = findEntity("chest_"..data.amount)
	
		if chest then  

				
				lengthNum = math.random(1,#loot)				--select an element of loot
				
				lengthNum2 = math.random(1,#loot[lengthNum])	--select an element of loot[lengthNum]
				
				--always spawn loot from different sections 
				local bool = false
				
				for x=1,#loot do
					if taboo[x] == lengthNum then
						bool = true
						break
					elseif taboo[x] == nil then
						taboo[x] = lengthNum
						break
					end
				end
		
			if lengthNum == 12 and math.random(1,5) < 5 or bool == true then 	--limitation for books
			--do nothing
			else
		
				chest.surface:addItem(spawn(loot[lengthNum][lengthNum2]).item)
				
				if lengthNum == 5 then hudPrint("lengthNum = 5") end
				if lengthNum > 4 and lengthNum < 8 then			--spawn additional for stackables (bombs,coins,stack.)
					for i=1,math.random(0,3) do 
						if lengthNum == 5 then 
							lengthNum2 = math.random(1,3)
						end	
						if lengthNum == 5 then					--spawn different coins, when coins are selected
							lengthNum2 = math.random(1,3)
							hudPrint(tostring(lengthNum))
						end
						
						chest.surface:addItem(spawn(loot[lengthNum][lengthNum2]).item)
					end
				end
			
				if (math.random(0,5) > 2 and data.reroll > 1) or data.reroll > 3  then --min. 2 spawns, max. 4 spawns
					data.amount = data.amount + 1				--next chest
					data.reroll = 1								--reset reroll
					for x=1,#taboo do 							--reset the taboo table to nil
						taboo[x] = nil
					end
					spreadContents(chest.surface,0.2,true)		--spread contents (function from dm example dungeon)
				else
					data.reroll = data.reroll + 1				--for reroll limitation
				end
			end
			
		else 
			data.amount = data.amount + 1						--go to next chest
		end
				
			if data.amount > data.maxamount  then			--all is done
				self.go:destroy()
			else
				delayedCall(self.go.id,0.04,'pimpmychests') 	--repeat function
			end
			
end

delayedCall(self.go.id,0.02,'pimpmychests')

function spreadContents(surface,spacing,rotate)
	local occupied = {}
	local size = surface:getSize()
	local xSize = size[1]*0.72
	local ySize = size[2]*0.72
	local oddFacing = surface.go.facing%2 ~= 0
	for n,item in surface:contents() do
		local xoff, yoff
		local tries = 0
		repeat
			xoff = math.random()*(xSize-spacing)-(xSize-spacing)/2
			yoff = math.random()*(ySize-spacing)-(ySize-spacing)/2
			tries = tries+1
			if tries > 1000 and debug.script.DEBUG then print("spread failed on "..surface.go.id) end
		until tries > 100 or not alreadyOccupied(occupied,xoff,yoff,spacing)
		local origx, origy = item.go:getSubtileOffset()
		if oddFacing then
			item.go:setSubtileOffset(origx+yoff,origy+xoff)
			table.insert(occupied,yoff)
			table.insert(occupied,xoff)
		else
			item.go:setSubtileOffset(origx+xoff,origy+yoff)
			table.insert(occupied,xoff)
			table.insert(occupied,yoff)
		end
		if rotate then
			item.go:setWorldRotationAngles(0,math.random()*360,0)
		end
	end
end

function alreadyOccupied(table,x,y,spacing)
	for i = 1, (#table)/2 do
		if math.abs(table[i]-x) < spacing and math.abs(table[i+1]-y) < spacing then
			return true
		end
	end
	return false
end
Version with Sacks:

Code: Select all

	loot  		= {}
	data		= {amount,maxamount,reroll}
	taboo		= {}

function init()
--[[style  ]]	loot[1]		= {"dagger","dm_pod_dust","rock","dm_bone","blowpipe","dm_ashes","dm_screws","dm_shredded_rags","horn","island_map","scope","skull","vilson_orb","dm_weapon_eye_of_time","dm_weapon_stormring","flute","timepiece","sling","compass","mortar","dm_flask_water","dm_flask_empty","flask"}
--[[food   ]]	loot[2]		= {"baked_maggot","blueberry_pie","boiled_beetle","borra","bread","cheese","dm_food_bread","dm_food_cheese","dm_food_cheese_fresh","dm_food_corn","dm_food_mushroom_slice","fjeld_warg_meat","herder_cap","horned_fruit","ice_lizard_steak","lizard_stick","mole_jerky","pitroot_bread","rat_shank","rat_swarm_shank","rotten_pitroot_bread","sausage","silver_roach","smoked_bass","snail_slice","snake_tail","turtle_eggs","turtle_steak","warg_meat"}
--[[herbs  ]]	loot[3]		= {"blackmoss","blooddrop_cap","crystal_flower","etherweed","falconskyre","mudwort"}
--[[potions]]	loot[4]		= {"potion_bear_form","potion_cure_disease","potion_cure_poison","potion_dexterity","potion_energy","potion_greater_energy","potion_greater_healing","potion_healing","potion_poison","potion_rage","potion_resurrection","potion_shield","potion_speed"}
--[[coins  ]]	loot[5]		= {"dm_coin_copper","dm_coin_silver","dm_coin_gold"}
--[[bombs  ]]	loot[6]		= {"dm_bomb_ful","dm_bomb_ven","fire_bomb","frost_bomb","poison_bomb","shock_bomb"}
--[[stack. ]]	loot[7]		= {"pellet_box","throwing_knife","shuriken","cannon_ball","dm_weapon_dart","dm_weapon_dart_poisoned","dart","sleep_dart"}
--[[scrolls]]	loot[8]		= {"scroll_darkbolt","scroll_darkness","scroll_dispel","scroll_fire_shield","scroll_fireball","scroll_fireburst","scroll_force_field","scroll_frost_shield","scroll_frostbolt","scroll_ice_shards","scroll_invisibility","scroll_light","scroll_lightning_bolt","scroll_meteor_storm","scroll_poison_bolt","scroll_poison_cloud","scroll_poison_shield","scroll_shield","scroll_shock","scroll_shock_shield"}
--[[amulets]]	loot[9]		= {"ring_string","dm_necklace_choker","dm_necklace_ekkhard_cross","dm_necklace_feral_pendant","dm_necklace_gem_ages","dm_necklace_hellion","dm_necklace_illumulet","dm_necklace_moonstone","dm_necklace_symal","frostbite_necklace","gear_necklace","runestone_necklace","storm_amulet","bone_amulet","crystal_amulet","nergal_amulet","neck_chain","jewel_pendant","spirit_mirror_pendant","spiritwalker_pendant"}
--[[bracers]]	loot[10]	= {"brace_fortitude","bracelet_tirin","bronze_brace","hardstone_bracelet","leafbond_bracelet","serpent_bracer","steel_armband","dm_rabbits_foot"}
--[[shards ]]	loot[11]	= {"crystal_shard_healing","crystal_shard_protection","crystal_shard_recharging"}
--[[perma. ]]	loot[12]	= {"potion_strength","potion_vitality","potion_willpower","tome_air","tome_earth","tome_energy","tome_fire","tome_health","tome_leadership","tome_moon","tome_water","tome_wisdom"}
	
	data.amount		= 0 --current chest
	data.maxamount	= 1 --put your highest chestnumber here
	data.reroll		= 1 --for spawning multiple times
end

delayedCall(self.go.id,0.01,'init')
	
function pimpmychests()
	local lengthNum  = 0 --length of loot 
	local lengthNum2 = 0 --length of loot[lengthNum]
	
	local chest = findEntity("chest_"..data.amount)
	
		if chest then  

				lengthNum  = math.random(1,#loot)				--select an element of loot
			
				lengthNum2 = math.random(1,#loot[lengthNum])	--select an element of loot[lengthNum]
				
				--always spawn loot from different sections 
				local bool = false
				
				for x=1,#loot do
					if taboo[x] == lengthNum then
						bool = true
						break
					elseif taboo[x] == nil then
						taboo[x] = lengthNum
						break
					end
				end
		
		
			if lengthNum == 12 and math.random(1,5) < 5 or bool == true then 	--limitation for books
			--do nothing
			else
				
				local sack = nil
  				for v,i in chest.surface:contents() do	--chance of spawning into a sack, if there is one
  					 if i.go.name == "sack" then 
						if math.random(1,3) == 1 then 
							i.go.containeritem:addItem(spawn(loot[lengthNum][lengthNum2]).item)
							sack = i
						else
							chest.surface:addItem(spawn(loot[lengthNum][lengthNum2]).item)
						end
						bool = true
						break
  					 end
				end
				
				if bool == false then 					--chance to spawn a sack and spawn the loot in it
					if math.random(1,6) == 1 then
						sack = self.go:spawn("sack").item
						chest.surface:addItem(sack.go.item)
						sack.go.containeritem:addItem(spawn(loot[lengthNum][lengthNum2]).item)
					else
						chest.surface:addItem(spawn(loot[lengthNum][lengthNum2]).item)
					end
				end
				
				if lengthNum > 4 and lengthNum < 8 then		--spawn additional for stackables (bombs,coins,stack.)
					
					for i=1,math.random(0,3) do 				--for spawning different coins
						if lengthNum == 5 then 
							lengthNum2 = math.random(1,3)
						end		
						if math.random(1,3) == 1 and sack then
							sack.go.containeritem:addItem(spawn(loot[lengthNum][lengthNum2]).item)
						else 
							chest.surface:addItem(spawn(loot[lengthNum][lengthNum2]).item)
						end
					end
				end
			
				if (math.random(0,5) > 2 and data.reroll > 1) or data.reroll > 3  then --min. 2 spawns, max. 4 spawns
					data.amount = data.amount + 1				--next chest
					data.reroll = 1								--reset reroll
					for x=1,#taboo do 							--reset the taboo table to nil
						taboo[x] = nil
					end
					spreadContents(chest.surface,0.2,true)		--spread contents (function from dm example dungeon)
				else
					data.reroll = data.reroll + 1				--for reroll limitation
				end
			end
			
		else 
			data.amount = data.amount + 1						--go to next chest
		end
				
			if data.amount > data.maxamount  then			--all is done
				self.go:destroy()
			else
				delayedCall(self.go.id,0.04,'pimpmychests') 	--repeat function
			end
			
end

delayedCall(self.go.id,0.02,'pimpmychests')

function spreadContents(surface,spacing,rotate)
	local occupied = {}
	local size = surface:getSize()
	local xSize = size[1]*0.72
	local ySize = size[2]*0.72
	local oddFacing = surface.go.facing%2 ~= 0
	for n,item in surface:contents() do
		local xoff, yoff
		local tries = 0
		repeat
			xoff = math.random()*(xSize-spacing)-(xSize-spacing)/2
			yoff = math.random()*(ySize-spacing)-(ySize-spacing)/2
			tries = tries+1
			if tries > 1000 and debug.script.DEBUG then print("spread failed on "..surface.go.id) end
		until tries > 100 or not alreadyOccupied(occupied,xoff,yoff,spacing)
		local origx, origy = item.go:getSubtileOffset()
		if oddFacing then
			item.go:setSubtileOffset(origx+yoff,origy+xoff)
			table.insert(occupied,yoff)
			table.insert(occupied,xoff)
		else
			item.go:setSubtileOffset(origx+xoff,origy+yoff)
			table.insert(occupied,xoff)
			table.insert(occupied,yoff)
		end
		if rotate then
			item.go:setWorldRotationAngles(0,math.random()*360,0)
		end
	end
end

function alreadyOccupied(table,x,y,spacing)
	for i = 1, (#table)/2 do
		if math.abs(table[i]-x) < spacing and math.abs(table[i+1]-y) < spacing then
			return true
		end
	end
	return false
end
For your Tables.
Here is a little extra for making tables more interesting. It adds some more or less unimportant items to your empty tables

Code: Select all

items = {"pellet_box","dm_coin_gold","dm_coin_silver","dm_coin_copper","dm_weapon_stormring","dm_weapon_eye_of_time","flute","throwing_knife","timepiece","sling","dm_bone","shuriken","compass","mortar","dm_flask_water","dm_flask_empty","dagger"}

amount = 1
maxamount = 5 --highest index table here
	

function pimpmytables()
	
	local table = findEntity("dm_surface_table_square_"..amount) --or "dm_surface_table_round_"
	
	if table then 
		math.randomseed(Time.systemTime()*1000)
		for i=0,math.random(0,2) do
			local lootpick = math.random(1,#items)
			table.surface:addItem(spawn(items[lootpick]).item)
		end
		spreadContents(table.surface,0.2,true)		--spread contents (function from dm example dungeon)
	end	
	
	amount = amount + 1
		
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.01,'pimpmytables') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmytables')	
Next up are AdrTrus Racks.
If you want to use it you have to include the Multi Alcove Manager pack from AdrTru
Here is a link to it:
https://www.nexusmods.com/legendofgrimrock2/mods/21/
Version for Swordracks
Levelscaled from Level 1 to 5. Can be changed with a little bit of Scripting experience.
Loot can be divided as however you want.
Please follow this id Syntax, so the racks are found by the script: lvl_5_swordrack_1 (Equips rack with lvl 1-5 loot)
Please set maxamount to the highest endindex your racks have

Code: Select all

swords = {}
levelcount = 1
amount = 1
maxamount = 1 --highest index rack here 

function init()
swords[1] = {"machete","long_sword"}
swords[2] = {"cutlass","fire_blade"}
swords[3] = {"lightning_blade","two_handed_sword"}
swords[4] = {"sickle_sword","sabre"}
swords[5] = {"ancient_claymore"}
end

delayedCall(self.go.id,0.01,'init')

function pimpmyswordracks()

local rack = findEntity("lvl_"..levelcount.."_swordrack_"..amount)
	if rack then 
		local taboo = 0				
		for i = 1,#swords do
			local lootnum = math.random(1,levelcount)
				if taboo == lootnum then
					break
				end
			local loot = math.random(1,#swords[levelcount])	
			rack.surface:addItem(spawn(swords[lootnum][loot]).item)
			taboo = lootnum	
		end
	end
	
	if levelcount < #swords then
		levelcount = levelcount + 1
	else 
		levelcount = 1
		amount = amount + 1
	end
	
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.04,'pimpmyswordracks') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmyswordracks')
Version for Weaponracks.
Levelscaled from Level 1 to 5. Can be changed with a little bit of Scripting experience.
Loot can be divided as however you want.
Please follow this id Syntax, so the racks are found by the script: lvl_5_weaponrack_1 (Equips rack with lvl 1-5 loot)
Please set maxamount to the highest endindex your racks have

Code: Select all

loot = {}
levelcount = 1
amount = 1
taboo = {}
maxamount = 5 --highest index rack here


function init()
--[[swords]]	loot[1] = {}
				loot[1][1] = {"machete","long_sword","rapier"}
				loot[1][2] = {"cutlass","fire_blade","lightning_blade"}
				loot[1][3] = {"two_handed_sword","sickle_sword"}
				loot[1][4] = {"serpent_blade","sabre"}
				loot[1][5] = {"bone_blade","ancient_claymore"}
--[[bows]]		loot[2] = {}
				loot[2][1] = {"short_bow"}
				loot[2][2] = nil
				loot[2][3] = {"longbow"}
				loot[2][4] = {"lightning_bow"}
				loot[2][5] = nil
--[[firearms]]	loot[3] = {}
				loot[3][1] = {"flintlock"}
				loot[3][2] = {"revolver"}
				loot[3][3] = {"arquebus"}
				loot[3][4] = nil
				loot[3][5] = {"repeater"}
--[[wands]]		loot[4] = {}
				loot[4][1] = {"whitewood_wand"}
				loot[4][2] = {"serpent_staff"}
				loot[4][3] = {"lightning_rod"}
				loot[4][4] = {"shaman_staff"}
				loot[4][5] = {"wizards_virge"} --very op
--[[staffs]]	loot[5] = {}
				loot[5][1] = {"tribal_spear"}
				loot[5][2] = {"zarchton_harpoon"}
				loot[5][3] = {"acolyte_staff"}
				loot[5][4] = {"quarterstaff"}
				loot[5][5] = nil
--[[daggers]]	loot[6] = {}
				loot[6][1] = {"dagger"}
				loot[6][2] = {"fist_dagger"}
				loot[6][3] = {"venom_edge"}
				loot[6][4] = {"assassin_dagger","ethereal_blade"}
				loot[6][5] = {"moonblade"}
--[[axes]]		loot[7] = {}
				loot[7][1] = {"hand_axe"}
				loot[7][2] = {"skullcleave","pickaxe"}
				loot[7][3] = {"battle_axe","ax"}
				loot[7][4] = {"venomfang_pick","great_axe","poleaxe"}
				loot[7][5] = {"scythe","bane"}
--[[ammo]]		loot[8] = {}
				loot[8][1] = {"arrow"}
				loot[8][2] = {"cold_arrow"}
				loot[8][3] = {"fire_arrow"}
				loot[8][4] = {"shock_arrow"}
				loot[8][5] = nil
end

delayedCall(self.go.id,0.01,'init')

function pimpmyweaponracks()
	
	local rack = findEntity("lvl_"..levelcount.."_weaponrack_"..amount)
	
	if rack then 
		for i=1, math.random(1,3) do
			addloot()
		end
		for x=1,#taboo do 							--reset the taboo table to nil
			taboo[x] = nil
		end
	end	
	
		if levelcount < #loot[1] then 
			levelcount = levelcount + 1
		else 
			levelcount = 1
			amount = amount + 1
		end
		
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.01,'pimpmyweaponracks') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmyweaponracks')
	
function addloot()
	local bool = false
	local rack = findEntity("lvl_"..levelcount.."_weaponrack_"..amount)
	
	math.randomseed(Time.systemTime()*1000)
	local lootNum = math.random(1,#loot)
	local levelNum = math.random(1,levelcount)
	
		for x=1,#loot do
			if taboo[x] == lootNum then
				bool = true
				break
			elseif taboo[x] == nil then
				taboo[x] = lootNum						
				break
			end
		end
				
	if loot[lootNum][levelNum] and bool == false then
			lootpick = math.random(1,#loot[lootNum][levelNum])
			rack.surface:addItem(spawn(loot[lootNum][levelNum][lootpick]).item)
			
			if lootNum == 2 then 
				for i=1,math.random(1,3) do 
					rack.surface:addItem(spawn(loot[8][math.random(1,4)][1]).item)
				end
			end
	else
		addloot()
	end
end
Version for Armorracks.
Levelscaled from Level 1 to 5. Can be changed with a little bit of Scripting experience.
Loot can be divided as however you want.
Please follow this id Syntax, so the racks are found by the script: lvl_5_armorrack_1 (Equips rack with lvl 1-5 loot)
Please set maxamount to the highest endindex your racks have

Code: Select all

loot = {}
levelcount = 1
amount = 1
taboo = {}
maxamount = 5 --highest index rack here
	
function init()
--[[shields]]	loot[1] = {}
				loot[1][1] = {"tribal_shield","skeleton_knight_shield","makeshift_buckler"}
				loot[1][2] = {"round_shield","heavy_shield"}
				loot[1][3] = {"pearl_shield"}
				loot[1][4] = {"shield_elements"}
				loot[1][5] = {"meteor_shield","crystal_shield"}
--[[helms]]		loot[2] = {}
				loot[2][1] = {"reed_helmet","skeleton_knight_helmet"}
				loot[2][2] = {"bear_skull_helm","legionary_helmet"}
				loot[2][3] = {"iron_basinet"}
				loot[2][4] = {"full_helmet","helmet_valor"}
				loot[2][5] = {"meteor_helmet","crystal_helmet"}
--[[caps]]		loot[3] = {}
				loot[3][1] = {"circlet_war","makeshift_mask","leather_cap"}
				loot[3][2] = {"embalmers_headpiece"}
				loot[3][3] = {"archmage_hat","conjurers_hat"}
				loot[3][4] = {"flarefeather_cap","chitin_mask"}
				loot[3][5] = nil
--[[chests]]	loot[4] = {}
				loot[4][1] = {"makeshift_chestplate","reed_cuirass","leather_brigandine"}
				loot[4][2] = {"chitin_mail"}
				loot[4][3] = {"ring_mail","plate_cuirass","mirror_chestplate"}
				loot[4][4] = {"cuirass_valor"}
				loot[4][5] = {"meteor_cuirass","crystal_cuirass"} 
--[[legs]]		loot[5] = {}
				loot[5][1] = {"leather_cuisse","makeshift_legplates","reed_legmail"}
				loot[5][2] = {"chitin_cuisse"}
				loot[5][3] = {"ring_cuisse","plate_cuisse"}
				loot[5][4] = {"cuisse_valor"}
				loot[5][5] = {"meteor_cuisse","crystal_greaves"}
--[[gauntlets]]	loot[6] = {}
				loot[6][1] = {"leather_gloves","reed_gauntlets"}
				loot[6][2] = {"nomad_mittens","bearclaw_gauntlets"}
				loot[6][3] = {"mirror_gauntlets","fire_gauntlets","pit_gauntlets","ring_gauntlets","plate_gauntlets"}
				loot[6][4] = {"rogue_gloves","gauntlets_valor"}
				loot[6][5] = {"meteor_gauntlets","crystal_gauntlets"}
--[[boots]]		loot[7] = {}
				loot[7][1] = {"shoes","sandals","reed_sabaton","leather_boots"}
				loot[7][2] = {"embalmers_boots","pointy_shoes","nomad_boots"}
				loot[7][3] = {"archmage_loafers","chitin_greaves","mirror_greaves","lurker_boots","ring_greaves","plate_greaves"}
				loot[7][4] = {"rogue_boots","greaves_valor"}
				loot[7][5] = {"meteor_boots","crystal_boots"}
end

delayedCall(self.go.id,0.01,'init')

function pimpmyarmorracks()
	
	local rack = findEntity("lvl_"..levelcount.."_armorrack_"..amount)
	
	if rack then 
	math.randomseed(Time.systemTime()*1000)
		for i=1, math.random(1,3) do
			addloot()
		end
		for x=1,#taboo do 							--reset the taboo table to nil
			taboo[x] = nil
		end
	end	
	
		if levelcount < #loot[1] then 
			levelcount = levelcount + 1
		else 
			levelcount = 1
			amount = amount + 1
		end
		
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.01,'pimpmyarmorracks') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmyarmorracks')
	
function addloot()
	local bool = false
	local rack = findEntity("lvl_"..levelcount.."_armorrack_"..amount)
	
	math.randomseed(Time.systemTime()*1000)
	local lootNum = math.random(1,#loot)
	local levelNum = math.random(1,levelcount)
	
		for x=1,#loot do
			if taboo[x] == lootNum then
				bool = true
				break
			elseif taboo[x] == nil then
				taboo[x] = lootNum						
				break
			end
		end
				
	if loot[lootNum][levelNum] and bool == false then
			lootpick = math.random(1,#loot[lootNum][levelNum])
			rack.surface:addItem(spawn(loot[lootNum][levelNum][lootpick]).item)
	else
		addloot()
	end
end
Version for Wallhookracks.
Levelscaled from Level 1 to 5. Can be changed with a little bit of Scripting experience.
Loot can be divided as however you want.
Please follow this id Syntax, so the racks are found by the script: lvl_5_wallhookrack_1 (Equips rack with lvl 1-5 loot)
Please set maxamount to the highest endindex your racks have

Code: Select all

loot = {}
levelcount = 1
amount = 1
maxamount = 5 --highest index rack here
	
function init()
--[[shields]]		loot[1] = {"tribal_shield","makeshift_buckler"}
				loot[2] = {"round_shield","skeleton_knight_shield"}
				loot[3] = {"pearl_shield","heavy_shield"}
				loot[4] = {"shield_elements"}
				loot[5] = {"meteor_shield","crystal_shield"}
end

delayedCall(self.go.id,0.01,'init')

function pimpmywallhookracks()
	
	local rack = findEntity("lvl_"..levelcount.."_wallhookrack_"..amount)
	
	if rack then 
		math.randomseed(Time.systemTime()*1000)
		local lootNum = math.random(1,levelcount)
		local lootpick = math.random(1,#loot[lootNum])
		rack.surface:addItem(spawn(loot[lootNum][lootpick]).item)
	end	
	
		if levelcount < #loot then 
			levelcount = levelcount + 1
		else 
			levelcount = 1
			amount = amount + 1
		end
		
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.01,'pimpmywallhookracks') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmywallhookracks')
A little Extra for "sx_barrel_chest" and "sx_barrel_chest_02". (Turns your Barrels into foodbarrels.)
(sx_town_tileset and dm_set needed)

Code: Select all

items	= {"baked_maggot","blueberry_pie","boiled_beetle","borra","bread","cheese","dm_food_bread","dm_food_cheese","dm_food_cheese_fresh","dm_food_corn","dm_food_mushroom_slice","fjeld_warg_meat","herder_cap","horned_fruit","ice_lizard_steak","lizard_stick","mole_jerky","pitroot_bread","rat_shank","rat_swarm_shank","rotten_pitroot_bread","sausage","silver_roach","smoked_bass","snail_slice","snake_tail","turtle_eggs","turtle_steak","warg_meat"}

amount = 1
maxamount = 5 --highest index barrel here

function pimpmybarrels()
	
	local barrel = findEntity("sx_barrel_chest_"..amount) --or "sx_barrel_chest_02_"
	
	if barrel then 
		math.randomseed(Time.systemTime()*1000)
		for i=0,math.random(0,2) do
			local lootpick = math.random(1,#items)
			barrel.surface:addItem(spawn(items[lootpick]).item)
		end
		spreadContents(barrel.surface,0.2,true)		--spread contents (function from dm example dungeon)
	end	
	
	amount = amount + 1
		
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.01,'pimpmybarrels') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmybarrels')	
	
function spreadContents(surface,spacing,rotate)
	local occupied = {}
	local size = surface:getSize()
	local xSize = size[1]*0.72
	local ySize = size[2]*0.72
	local oddFacing = surface.go.facing%2 ~= 0
	for n,item in surface:contents() do
		local xoff, yoff
		local tries = 0
		repeat
			xoff = math.random()*(xSize-spacing)-(xSize-spacing)/2
			yoff = math.random()*(ySize-spacing)-(ySize-spacing)/2
			tries = tries+1
			if tries > 1000 and debug.script.DEBUG then print("spread failed on "..surface.go.id) end
		until tries > 100 or not alreadyOccupied(occupied,xoff,yoff,spacing)
		local origx, origy = item.go:getSubtileOffset()
		if oddFacing then
			item.go:setSubtileOffset(origx+yoff,origy+xoff)
			table.insert(occupied,yoff)
			table.insert(occupied,xoff)
		else
			item.go:setSubtileOffset(origx+xoff,origy+yoff)
			table.insert(occupied,xoff)
			table.insert(occupied,yoff)
		end
		if rotate then
			item.go:setWorldRotationAngles(0,math.random()*360,0)
		end
	end
end

function alreadyOccupied(table,x,y,spacing)
	for i = 1, (#table)/2 do
		if math.abs(table[i]-x) < spacing and math.abs(table[i+1]-y) < spacing then
			return true
		end
	end
	return false
end
And here is something for the "shipwreck_big_rope_chest". (Adds all Kinds of bombs to the ropechests)
(sx_town_tileset and dm_set needed)

Code: Select all

items = {"cannon_ball","dm_bomb_ful","dm_bomb_magicbox_blue","dm_bomb_magicbox_green","dm_bomb_ven","dm_weapon_dart","dm_weapon_dart_poisoned","fire_bomb","frost_bomb","poison_bomb","shock_bomb"}

amount = 1
maxamount = 5 --highest index table here
	

function pimpmyrope()
	
	local rope = findEntity("shipwreck_big_rope_chest_"..amount) 
	
	if rope then 
		math.randomseed(Time.systemTime()*1000)
		for i=0,math.random(0,2) do
			local lootpick = math.random(1,#items)
			local item = spawn(items[lootpick],1,1,1,1,1)
			item.item:setStackSize(math.random(2,5))
			barrel.surface:addItem(item.item)rope.surface:addItem(spawn(items[lootpick]).item)
		end
		spreadContents(rope.surface,0.2,true)		--spread contents (function from dm example dungeon)
	end	
	
	amount = amount + 1
		
	if amount > maxamount then			--all is done
		self.go:destroy()
	else
		delayedCall(self.go.id,0.01,'pimpmyrope') 	--repeat function
	end
	
end

delayedCall(self.go.id,0.04,'pimpmyrope')	
	
function spreadContents(surface,spacing,rotate)
	local occupied = {}
	local size = surface:getSize()
	local xSize = size[1]*0.72
	local ySize = size[2]*0.72
	local oddFacing = surface.go.facing%2 ~= 0
	for n,item in surface:contents() do
		local xoff, yoff
		local tries = 0
		repeat
			xoff = math.random()*(xSize-spacing)-(xSize-spacing)/2
			yoff = math.random()*(ySize-spacing)-(ySize-spacing)/2
			tries = tries+1
			if tries > 1000 and debug.script.DEBUG then print("spread failed on "..surface.go.id) end
		until tries > 100 or not alreadyOccupied(occupied,xoff,yoff,spacing)
		local origx, origy = item.go:getSubtileOffset()
		if oddFacing then
			item.go:setSubtileOffset(origx+yoff,origy+xoff)
			table.insert(occupied,yoff)
			table.insert(occupied,xoff)
		else
			item.go:setSubtileOffset(origx+xoff,origy+yoff)
			table.insert(occupied,xoff)
			table.insert(occupied,yoff)
		end
		if rotate then
			item.go:setWorldRotationAngles(0,math.random()*360,0)
		end
	end
end

function alreadyOccupied(table,x,y,spacing)
	for i = 1, (#table)/2 do
		if math.abs(table[i]-x) < spacing and math.abs(table[i+1]-y) < spacing then
			return true
		end
	end
	return false
end
Any improvement proposals are welcome,
Xardas
Last edited by Xardas on Mon May 28, 2018 12:19 am, edited 32 times in total.
In order to get to the other side of the pit you have to get hit by the fireball and die....
Yep.....moving on!

User avatar
Curunir
Posts: 332
Joined: Fri Mar 30, 2012 11:19 pm

Re: Pimp my Chests

Post by Curunir » Mon May 07, 2018 10:25 pm

This seems like a neat idea, but I would do something else with it!

It would be great to be able to scale rewards from dig up secret chests with player level, sort of like Elder Scrolls does it, so that if the player forgets a treasure chest or maybe only solves a puzzle later on, he doesn't get level 3 loot but the loot in the chest scales with the current party level.

User avatar
THOM
Posts: 1130
Joined: Wed Nov 20, 2013 11:35 pm
Location: Germany - Cologne
Contact:

Re: Pimp my Chests

Post by THOM » Mon May 07, 2018 10:37 pm

Not a bad idea - but in many games/mods this is not necessary. Usually the layout is some kind of linear and the level of a party can be more or less be forseen. On map #3 the party is lvel 3 or 4 (something like that).

But if you do an open world layout that could be a good feature.
THOM formaly known as tschrage
_______________________________________________
My MOD (LoG1): Castle Ringfort Thread
My MOD (LoG2): Journey To Justice Thread | Download

User avatar
Xardas
Posts: 83
Joined: Fri Jul 28, 2017 12:30 am

Re: Pimp my Chests

Post by Xardas » Mon May 07, 2018 11:10 pm

I already made a levelscaled version for adding loot to AdrTrus mam_racks assets. You can choose from lvl1 to lvl4 loot (finer scale is easily possible).
I will add that to this thread maybe later.
Spawning loot depending on the Players level is also a really good idea. I'm going to think about that as well.
Maybe a redefined chest, which spawns a flootrigger connected to the function in front of it is possible.
For the beginning i want to create a bug free and maybe more detailed version of the chestscript above
In order to get to the other side of the pit you have to get hit by the fireball and die....
Yep.....moving on!

Zo Kath Ra
Posts: 829
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Re: Pimp my Chests

Post by Zo Kath Ra » Tue May 08, 2018 6:46 pm

Xardas wrote:To make this work, you must have the dm_ressource pack included to your init.lua file.
https://www.nexusmods.com/legendofgrimrock2/mods/48/

User avatar
Xardas
Posts: 83
Joined: Fri Jul 28, 2017 12:30 am

Re: Pimp my Chests

Post by Xardas » Fri Jun 01, 2018 5:25 pm

Now i found a way to spawn loot in a chest depending on the Players level.
The moment you open the chest the loot is spawned.
Here is the definition of the "chest_with_loot".

Code: Select all

defineObject{
	name = "chest_with_loot",
	components = {
		{
			class = "Model",
			model = "assets/models/env/treasure_chest.fbx",
		},
		{
			class = "Model",
			name = "lock",
			model = "assets/models/env/treasure_chest_lock.fbx",
			offset = vec(0, 0.41406, 0.54883),
			enabled = false,
		},
		{
			class = "Animation",
			animations = {
				open = "assets/animations/env/treasure_chest_open.fbx",
				close = "assets/animations/env/treasure_chest_close.fbx",
			}
		},	
		{
			 class = "Clickable",
			 name = 'lootclick',
			 offset = vec(0,0.5,0),
			 size = vec(1.5, 1.0, 1.0),
			 maxDistance = 1,
			--debugDraw = true,
			onClick = function(self)
					delayedCall("loot_up", 0.001, "addloot", self.go.id )
			end,
		},
		{
			class = "Clickable",
			offset = vec(0,0.5,0),
			size = vec(1.5, 1.0, 1.0),
			maxDistance = 1,
			--debugDraw = true,
		},
	
		{
			class = "Obstacle",
			hitSound = "barrel_hit",
		},
		{
			class = "ProjectileCollider",
		},
		{
			class = "Surface",
			offset = vec(0,0.153,0),
			size = vec(1.5, 0.85),
			--debugDraw = true,
		},		
		{
			class = "Chest",
		},
	},
	placement = "floor",
	editorIcon = 216,
	automapIcon = 124,
	automapIconLayer = 1,
}
And here is the script that has to be included to the dungeon. I´m still working with the dm set by the way. The Entity Name of the script has to be "loot_up".

Code: Select all

	loot  		= {}
	reroll		= 1 --for spawning multiple times
	taboo		= {}

function init()
--NOTE: Multiple items from one section will not be spawned due to game balancing reasons
--[[style  ]]	loot[1]		= {"dagger","dm_pod_dust","rock","dm_bone","blowpipe","dm_ashes","dm_screws","dm_shredded_rags","horn","island_map","scope","skull","vilson_orb","dm_weapon_eye_of_time","dm_weapon_stormring","flute","timepiece","sling","compass","mortar","dm_flask_water","dm_flask_empty","flask"}
--[[food   ]]	loot[2]		= {"baked_maggot","blueberry_pie","boiled_beetle","borra","bread","cheese","dm_food_bread","dm_food_cheese","dm_food_cheese_fresh","dm_food_corn","dm_food_mushroom_slice","fjeld_warg_meat","herder_cap","horned_fruit","ice_lizard_steak","lizard_stick","mole_jerky","pitroot_bread","rat_shank","rat_swarm_shank","rotten_pitroot_bread","sausage","silver_roach","smoked_bass","snail_slice","snake_tail","turtle_eggs","turtle_steak","warg_meat"}
--[[herbs  ]]	loot[3]		= {"blackmoss","blooddrop_cap","crystal_flower","etherweed","falconskyre","mudwort"}
--[[potions]]	loot[4]		= {"potion_bear_form","potion_cure_disease","potion_cure_poison","potion_energy","potion_greater_energy","potion_greater_healing","potion_healing","potion_poison","potion_rage","potion_resurrection","potion_shield","potion_speed"}
--[[coins  ]]	loot[5]		= {"dm_coin_copper","dm_coin_silver","dm_coin_gold"}
--[[bombs  ]]	loot[6]		= {"dm_bomb_ful","dm_bomb_ven","fire_bomb","frost_bomb","poison_bomb","shock_bomb"}
--[[stack. ]]	loot[7]		= {"pellet_box","throwing_knife","shuriken","cannon_ball","dm_weapon_dart","dm_weapon_dart_poisoned","dart","sleep_dart"}
--[[scrolls]]	loot[8]		= {"scroll_darkbolt","scroll_darkness","scroll_dispel","scroll_fire_shield","scroll_fireball","scroll_fireburst","scroll_force_field","scroll_frost_shield","scroll_frostbolt","scroll_ice_shards","scroll_invisibility","scroll_light","scroll_lightning_bolt","scroll_meteor_storm","scroll_poison_bolt","scroll_poison_cloud","scroll_poison_shield","scroll_shield","scroll_shock","scroll_shock_shield"}
--[[amulets]]	loot[9]		= {"ring_string","dm_necklace_choker","dm_necklace_ekkhard_cross","dm_necklace_feral_pendant","dm_necklace_gem_ages","dm_necklace_hellion","dm_necklace_illumulet","dm_necklace_moonstone","dm_necklace_symal","frostbite_necklace","gear_necklace","runestone_necklace","storm_amulet","bone_amulet","crystal_amulet","nergal_amulet","neck_chain","jewel_pendant","spirit_mirror_pendant","spiritwalker_pendant"}
--[[bracers]]	loot[10]	= {"brace_fortitude","bracelet_tirin","bronze_brace","hardstone_bracelet","leafbond_bracelet","serpent_bracer","steel_armband","dm_rabbits_foot"}
--[[shards ]]	loot[11]	= {"crystal_shard_healing","crystal_shard_protection","crystal_shard_recharging"}
--[[perma. ]]	loot[12]	= {"potion_dexterity","potion_strength","potion_vitality","potion_willpower","tome_air","tome_earth","tome_energy","tome_fire","tome_health","tome_leadership","tome_moon","tome_water","tome_wisdom"}
end
delayedCall(self.go.id,0.01,'init')

function pimpmychest(self)
		
		local chest = findEntity(self.id)
		if chest then
		
				local partylevel = 0 --gethighestchamplevel 
				for i=1,4 do
					if partylevel < party.party:getChampion(i):getLevel() then 
						partylevel = party.party:getChampion(i):getLevel()
					end
				end
				
				if partylevel > 12 then partylevel = 12 end --NOTE: max lvl reachable in LoG2 is lvl 14
				local lengthNum = math.random(1,partylevel)
				local lengthNum2 = math.random(1,#loot[lengthNum])	--select an element of loot[lengthNum]
				
				local bool = false
				for x=1,#loot do
					if taboo[x] == lengthNum then
						bool = true
						break
					elseif taboo[x] == nil then
						taboo[x] = lengthNum
						break
					end
				end
				
			if bool == false then
				local item = chest:spawn(loot[lengthNum][lengthNum2]).item
				if item.go.item:getStackable() == true then
					if not item.go.name:match("crystal") and not item.go.name:match("potion") then
						item.go.item:setStackSize(math.random(1,4))
					end
				end
				
				local sack = nil
  				for v,i in chest.surface:contents() do	--chance of adding loot to a sack, if there is one
  					 if i.go.name == "sack" then 
						if math.random(1,3) == 1 then 
							i.go.containeritem:addItem(item.go.item)
						else 
							chest.surface:addItem(item.go.item)
						end
						bool = true
						break
  					 end
				end
				
				if bool == false then 					--chance to spawn a sack and add the loot to it
					if math.random(1,6) == 1 then
						sack = self:spawn("sack").item
						chest.surface:addItem(sack.go.item)
						sack.go.containeritem:addItem(item.go.item)
					else 
						chest.surface:addItem(item.go.item)
					end
				end
			end
				if reroll > math.random(1,3)  then --min. 2 spawns, max. 4 spawns
					reroll = 1									--reset reroll
					for x=1,#taboo do 							--reset the taboo table to nil
						taboo[x] = nil
					end
					spreadContents(chest.surface,0.2,true)		--spread contents (function from dm example dungeon)
				else
					reroll = reroll + 1							
					pimpmychest(self)
				end
		end
end

function addloot(target)
	if target and type(target) == "string" then   
		target = findEntity(target)
		if target.chest:getLocked() == false then 	--chest not locked										
			target.lootclick:disable()
			pimpmychest(target)
			delayedCall(self.go.id,0.25,"fadeOut")
		end
	end
end

function spreadContents(surface,spacing,rotate)
	local occupied = {}
	local size = surface:getSize()
	local xSize = size[1]*0.72
	local ySize = size[2]*0.72
	local oddFacing = surface.go.facing%2 ~= 0
	for n,item in surface:contents() do
		local xoff, yoff
		local tries = 0
		repeat
			xoff = math.random()*(xSize-spacing)-(xSize-spacing)/2
			yoff = math.random()*(ySize-spacing)-(ySize-spacing)/2
			tries = tries+1
			if tries > 1000 and debug.script.DEBUG then print("spread failed on "..surface.go.id) end
		until tries > 100 or not alreadyOccupied(occupied,xoff,yoff,spacing)
		local origx, origy = item.go:getSubtileOffset()
		if oddFacing then
			item.go:setSubtileOffset(origx+yoff,origy+xoff)
			table.insert(occupied,yoff)
			table.insert(occupied,xoff)
		else
			item.go:setSubtileOffset(origx+xoff,origy+yoff)
			table.insert(occupied,xoff)
			table.insert(occupied,yoff)
		end
		if rotate then
			item.go:setWorldRotationAngles(0,math.random()*360,0)
		end
	end
end

function alreadyOccupied(table,x,y,spacing)
	for i = 1, (#table)/2 do
		if math.abs(table[i]-x) < spacing and math.abs(table[i+1]-y) < spacing then
			return true
		end
	end
	return false
end

function fadeOut()
	local delay,fade,color  =  1,5,0xFFD700 --gold; for other colors look at https://www.mediaevent.de/tutorial/farbcodes.html 
	GameMode.fadeOut(color, fade)	
	delayedCall(self.go.id, delay, 'fadeIn', fade, color)
end
	
function fadeIn(fade, color)
	fade, color = fade, color 
	GameMode.fadeIn(color, fade)	
end
And here is the levelup function to test the chests, if you are interested. Just hook it up to a button

Code: Select all

function levelup()
	for i=1,4 do
 		party.party:getChampion(i):levelUp()
	end
	hudPrint("Partymembers are now Level_"..tostring(party.party:getChampion(1):getLevel()))
end
The items are still in a categorized order.
I will make a Little example dungeon of all the random scripts i made.
I will probably change some things later on.
In order to get to the other side of the pit you have to get hit by the fireball and die....
Yep.....moving on!

Post Reply