Set up for Randomized Loot in dungeon or monster drops

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
Post Reply
User avatar
msyblade
Posts: 792
Joined: Fri Oct 12, 2012 4:40 am
Location: New Mexico, USA
Contact:

Set up for Randomized Loot in dungeon or monster drops

Post by msyblade »

I was wondering if any of u lua whiz's could whip together some general useful global functions for us "illiterate" modders. I would love for the community (me)to have instant access to randomizers for regular loot all of us can put in our dungeons to increase replayability. Something along the lines of seven categories,

1. Regular weapons : machete to cutlass's
2. Elite weapons : dismantler, Nex, etc. things you shouldnt have access to early on.
3. Regular armor : Leather thru chain everything
4. Elite Armor : Chitin,Valor etc...
5. Accessories : Bracers, magic headwear
6. Rogue weapons : Shuriken, throwing axes
7. Sheilds : duh.

This list is probably shortsighted in the moment, but if someone could throw these together in an hour or two (Think I could do it, but it would take 4 days and wouldn't work when I was done, So I'd end up here, asking you to help me). I think it will improve so many creations we release over the next few months. We just place the Global functions, and script whichever object we want to spawn "this category of object" on the fly, whala. Dynamic playthroughs (What if DM had THAT). hit this thread if this tickles your fancy or are knowledgeable enough to pull it off without actually removing hair strands from your cranium.(Okay, I MAY be using the term "all the modders" as a direct substitute for "I would love this", but lets not go clouding the issue with logic.
Currently conspiring with many modders on the "Legends of the Northern Realms"project.

"You have been captured by a psychopathic diety who needs a new plaything to torture."
Hotel Hades
User avatar
Wolfrug
Posts: 55
Joined: Wed Oct 03, 2012 6:56 pm

Re: Substantial job for a good scripter.

Post by Wolfrug »

Here you go, slightly edited from my Mordor dungeon:

Code: Select all

function randomLoot(object)

local all_loot = 
{
cave_nettle=75,
blooddrop_blossom=75,
grim_cap=75,
milkreed=75,
slime_bell=75,
tar_bead=75,
}
local spawned_loot = {}
for i,v in pairs(all_loot) do
if (math.random (1,100) <= v) then table.insert(spawned_loot,i)
end
end
-- print (spawned_loot[1], #spawned_loot)
local newitem=nil
	if (#spawned_loot>0) then
		local randomnr = math.random (1,#spawned_loot)
			for i=1,#spawned_loot do
				if (randomnr==i) then newitem=spawn(spawned_loot[i], object.level, object.x, object.y, math.random (0,3))
				end
				end
			end
	end
return newitem
	
end
Run this code as such:
scriptEntityName.randomLoot(object) <- with object being any world object with coordinates that determines the place where the loot will be placed (I've used spawners, although you can use anything really).

Explanation of all_loot table: the first entry is the thing being spawned, and the second is the likelihood of that item spawning. So what it does is it iterates through each item, and if it makes the cut, it's added to a second list (spawned_loot). From this list, one item is randomly picked and spawned. Therefore there's always a slight chance no item makes the list (unless you have somethng =100). If you want it to always spawn one of the things in the list, simply make them all =100 and they'll have an equally large chance of spawning.

For your individual maces, rogue gears etc, just make several copies of the function and change the all_loot table accordingly! Enjoy.

-Wolfrug
Try my Mordor: Depths of Dejenol LoG-ification. Feedback much appreciated!
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: Substantial job for a good scripter.

Post by JKos »

Hi, that sounded like a nice feature so I made a quick implementation of it. It was no biggie because I already had categorized item lists.

random_loot script entity

Code: Select all

 itemsList = {
	weapon = { "torch","torch_everburning","machete","long_sword","cutlass","nex_sword","dismantler","cudgel","knoffer","warhammer","flail","ogre_hammer","icefall_hammer","hand_axe","battle_axe","great_axe","ancient_axe","knife","dagger","fist_dagger","assassin_dagger","legionary_spear","venom_edge","venom_edge_empty","lightning_blade","lightning_blade_empty","lightning_rod","lightning_rod_empty","fire_blade","fire_blade_empty","power_weapon"},
	shield = {"legionary_shield","round_shield","heavy_shield","shield_elements","shield_valor"},
	accessory = {"frostbite_necklace","bone_amulet","fire_torc","spirit_mirror_pendant","gear_necklace","hardstone_bracelet","bracelet_tirin","brace_fortitude","huntsman_cloak","tattered_cloak","scaled_cloak","diviner_cloak","serpent_bracer","pit_gauntlets","nomad_mittens","leather_gloves"},
	bomb = {"fire_bomb","shock_bomb","frost_bomb","poison_bomb"},
	food = {"snail_slice","herder_cap","pitroot_bread","rotten_pitroot_bread","rat_shank","boiled_beetle","baked_maggot","ice_lizard_steak","mole_jerky","blueberry_pie"},
	missileweapon = {  "sling","short_bow","crossbow","longbow","arrow","fire_arrow","cold_arrow","poison_arrow","shock_arrow","quarrel","fire_quarrel","cold_quarrel","poison_quarrel","shock_quarrel"},
	tome = {"tome_health","tome_wisdom","tome_fire"},
	armor = {  "hide_vest","leather_brigandine","leather_greaves","leather_cap","leather_boots","ring_mail","ring_greaves","ring_gauntlets","ring_boots","legionary_helmet","iron_basinet","chitin_mail","chitin_greaves","chitin_boots","chitin_mask","plate_cuirass","plate_greaves","full_helmet","plate_gauntlets","plate_boots","cuirass_valor","greaves_valor","helmet_valor","gauntlets_valor","boots_valor"},
	cloth = {  "peasant_breeches","peasant_tunic","peasant_cap","loincloth","leather_pants","doublet","silk_hose","flarefeather_cap","conjurers_hat","circlet_war","lurker_pants","lurker_vest","lurker_hood","lurker_boots","sandals","pointy_shoes","nomad_boots"},
	herb = {"grim_cap","tar_bead","cave_nettle","slime_bell","blooddrop_blossom","milkreed"},
	machinepart = {  "machine_part_north","machine_part_east","machine_part_south","machine_part_west","machine_junk1","machine_junk2","machine_junk3","machine_junk4","machine_junk5","machine_junk6"},
	potion = { "flask","water_flask","potion_healing","potion_energy","potion_poison","potion_cure_poison","potion_cure_disease","potion_rage","potion_speed"},
	staff = {"whitewood_wand","magic_orb","shaman_staff","zhandul_orb"},
	treasure = {"golden_chalice","golden_figure","golden_goromorg","golden_dragon","golden_crown","golden_orb","ancient_apparatus"},
	container = {"sack","wooden_box","mortar"},
	key = {"iron_key","brass_key","gold_key","round_key","ornate_key","gear_key","prison_key"},
	miscitem = { "skull","blue_gem","green_gem","red_gem","compass","remains_of_toorum"},
	scroll = {  "scroll","note","scroll_light","scroll_darkness","scroll_fireburst","scroll_shock","scroll_fireball","scroll_frostbolt","scroll_ice_shards","scroll_poison_bolt","scroll_poison_cloud","scroll_lightning_bolt","scroll_enchant_fire_arrow","scroll_fire_shield","scroll_frost_shield","scroll_poison_shield","scroll_shock_shield","scroll_invisibility"},
	throwingweapon = { "rock","throwing_knife","shuriken","throwing_axe"},
	consumable = {"snail_slice","herder_cap","pitroot_bread","rotten_pitroot_bread","rat_shank","boiled_beetle","baked_maggot","ice_lizard_steak","mole_jerky","blueberry_pie",
		"grim_cap","water_flask","potion_healing","potion_energy","potion_poison","potion_cure_poison","potion_cure_disease","potion_rage","potion_speed"
	}
}	
function addRandomLoot(monster,itemCategories,amountMin,amountMax)
	amountMin = amountMin or 1
	amountMax = amountMax or 1
	local amount = math.random(amountMin,amountMax)
	for i=1,amount do
		local category = itemCategories[math.random(#itemCategories)]
		local itemName = getRamdomItemName(category)		
		monster:addItem(spawn(itemName))
		--print(itemName)
	end
end

function getRamdomItemName(category)
	return itemsList[category][math.random(#itemsList[category])]
end

Code: Select all

random_loot.addRandomLoot(snail_1,{'weapon','potion'})
It's not perfect yet but a good start. There should be level categories for items too, because now any weapon could added to snail. But of course you can remove items which you don't want to be spawned from the itemsList .
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
Komag
Posts: 3658
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: Substantial job for a good scripter.

Post by Komag »

I can see this as being quite a handy development! I think I prefer specific placement, but random loot has its place
Finished Dungeons - complete mods to play
Decayer
Posts: 65
Joined: Sat Oct 13, 2012 3:19 pm

Re: Substantial job for a good scripter.

Post by Decayer »

Here's some stuff I've fiddled with:

Code: Select all

function getDefaultLootTable()
	local loot = {}
	
	loot[#loot + 1] = {category = "sword", {"machete", 1, 1.0}, {"long_sword", 1, 1.0}, {"cutlass", 2, 1.0}, {"nex_sword", 3, 1.0}, {"dismantler", 3, 1.0}}
	loot[#loot + 1] = {category = "mace", {"cudgel", 1, 1.0}, {"knoffer", 1, 1.0}, {"warhammer", 2, 1.0}, {"flail", 2, 1.0}, {"ogre_hammer", 3, 1.0}, {"icefall_hammer", 3, 1.0}}
	loot[#loot + 1] = {category = "axe", {"hand_axe", 1, 1.0}, {"battle_axe", 1, 1.0}, {"great_axe", 2, 1.0}, {"ancient_axe", 3, 1.0}}
	loot[#loot + 1] = {category = "dagger", {"knife", 1, 1.0}, {"dagger", 1, 1.0}, {"fist_dagger", 2, 1.0}, {"assassin_dagger", 3, 1.0}}
	
	return loot
end

function makeLootTableFromList(...)
	-- this doesn't work for now
	local loot = {{category = "simple"}}
	
	for i = 1,arg.n*3,3 do
		loot[1] = {arg[i], arg[i + 1], arg[i + 2]}
		print(loot[1])
	end
	
	return loot
end

function pickItemName(caller, options)
	local doDebug = false
	
	local loot = options[1] or getDefaultLootTable()
	local category = options[2]
	local minTier = options[3]
	local maxTier = options[4] or minTier
	
	if type(category) == "string" then
		category = {category}
	end
	
	if type(category) ~= "table" then
		if doDebug then
			print(self.id .. ": category type " .. type(category) .. " is not supported")
		end
		return nil
	end
	
	local count = 0
	
	for i = 1,#loot do
		for j = 1,#category do
			if loot[i].category == category[j] then
				count = count + 1
			end
		end
	end

	if count == 0 then
		print(self.id .. ": loot table does not have any of the supplied categories")
		return nil
	elseif #category ~= count then
		if doDebug then
			print(self.id .. ": loot table does not have " .. #loot - count .. " of the supplied categories")
		end
	end
	
	local runaway = 0
	
	repeat
		local done = false
		local r1 = math.random(1, #loot)
		local r2 = math.random(1, #loot[r1])

		if loot[r1][r2][2] >= minTier and loot[r1][r2][2] <= maxTier then
			if math.random() <= loot[r1][r2][3] then
				item = loot[r1][r2][1]
				done = true
				break
			elseif doDebug then
				print(self.id .. ": ignoring " .. loot[r1][r2][1] .. "; its chance (" .. loot[r1][r2][3] .. ") did not satisfy random roll")
			end
		elseif doDebug then
			print(self.id .. ": ignoring " .. loot[r1][r2][1] .. "; its tier (" .. loot[r1][r2][2] .. ") is not " .. minTier .. " - " .. maxTier)
		end
		
		runaway = runaway + 1
		
		if runaway == 1000000 then
			print(self.id .. ": item selector caused runaway")
			for i = 1,#loot do
				print(loot[i].category)
			end
			return nil
		end
	until done
	
	return item
end

function spawnItemOnFloor(caller, options, level, x, y, facing, id)
	local item = pickItemName(caller, options)
	
	if item then
		return spawn(item, level, x, y, facing, id)
	end
end

function spawnItemOnMonster(caller, options, monster, id)
	local item = spawnItemInLimbo(caller, options, id)
	
	if item then
		if monster then
			monster:addItem(item)
			return item
		else
			print(self.id .. ": tried to add item to inexistent entity")
			return
		end
	end
end

function spawnItemInAlcove(caller, options, alcove, id)
	return spawnItemOnMonster(caller, options, alcove, id)
end

function spawnItemInLimbo(caller, options, id)
	return spawnItemOnFloor(caller, options, nil, nil, nil, nil, id)
end
Here's an example script:

Code: Select all

function trigger()
	-- spawns a random sword, mace, axe or dagger from tiers 1-3.
	loot_generator:spawnItemInAlcove({nil, {"sword", "mace", "axe", "dagger"}, 1, 3}, example_alcove)
	
	-- works, but you get a warning if debug is on since the default loot table doesn't have a category called "giblets".
	loot_generator:spawnItemInAlcove({nil, {"sword", "mace", "axe", "giblets"}, 1, 3}, example_alcove)

	-- doesn't work, the generator needs at least one valid category to work with.
	loot_generator:spawnItemInAlcove({nil, {"giblets"}, 1, 3}, example_alcove)
	
	-- you may supply a string if there's only one category.
	loot_generator:spawnItemInAlcove({nil, "sword", 1, 3}, example_alcove)
	
	-- let's put a tier 1 axe in the square to the east of this script entity and give it the id "Fred".
	if not findEntity("Fred") then
		loot_generator:spawnItemOnFloor({nil, "axe", 1}, self.level, self.x + 1, self.y, self.facing, "Fred")
	end
	
	-- this monster needs a mace to go clubbing. Get it? HAHAHAHAaaa! *commits suicide*
	loot_generator:spawnItemOnMonster({nil, "mace", 1, 3}, example_monster)
	
	-- spawns an item but doesn't put actually put it anywhere. Don't forget to get the return value if you do this.
	local my_item = loot_generator:spawnItemInLimbo({nil, {"sword", "mace", "axe", "dagger"}, 1, 3})

	-- let's try a custom loot table (that alcove is getting pretty full by now, by the way).
	-- this will always return a food item (biased towards a baked maggot) since the junk items are tier 1 and we want a tier 2 item.
	-- the rotten pitroot bread won't be included either since it is tier 3.
	loot_generator:spawnItemInAlcove({{{category = "junk", {"skull", 1, 1}, {"rock", 1, 1}}, {category = "food", {"baked_maggot", 2, 1}, {"boiled_beetle", 2, 0.25}, {"rotten_pitroot_bread", 3, 1}}}, {"skull", "food"}, 2}, example_alcove)
end
The format of a loot table category entry is {category = "category name", {"item name", tier, bias}}.
The bias is a value between 0 and 1 that represents how likely that item is to be accepted if it is chosen by the mighty Random Roll.

That should cover most of it. I haven't tested or optimized it really thoroughly, but I hope it will be useful to someone.
User avatar
Komag
Posts: 3658
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: Set up for Randomized Loot in dungeon or monster drops

Post by Komag »

added to Editing Superthread 8-)
Finished Dungeons - complete mods to play
User avatar
Fhizban
Posts: 68
Joined: Sun Oct 21, 2012 2:57 pm
Location: Germany
Contact:

Re: Set up for Randomized Loot in dungeon or monster drops

Post by Fhizban »

thanks a lot. those scripts are very helpful.

this is by the way exactly what i am trying to do with my "armory mod". it will turn all item generation inside the game into a nethack like random generation.
--
Fhizban's Asset Repository - the place where you find all my LoG contributions:
viewtopic.php?f=14&t=3904
Ixnatifual

Re: Set up for Randomized Loot in dungeon or monster drops

Post by Ixnatifual »

It would be pretty cool if the treasure generation would take dungeon level into account as well so you don't get plate drops and Dismantlers early on.
User avatar
Fhizban
Posts: 68
Joined: Sun Oct 21, 2012 2:57 pm
Location: Germany
Contact:

Re: Set up for Randomized Loot in dungeon or monster drops

Post by Fhizban »

@Ixnatifual my script will! Im taking into consideration the current dungeon level as well as the total party level. still working on the balancing though...
--
Fhizban's Asset Repository - the place where you find all my LoG contributions:
viewtopic.php?f=14&t=3904
Post Reply