Page 1 of 1

Set up for Randomized Loot in dungeon or monster drops

Posted: Sat Oct 20, 2012 6:13 am
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.

Re: Substantial job for a good scripter.

Posted: Sat Oct 20, 2012 9:59 am
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

Re: Substantial job for a good scripter.

Posted: Sat Oct 20, 2012 10:03 am
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 .

Re: Substantial job for a good scripter.

Posted: Sat Oct 20, 2012 11:29 am
by Komag
I can see this as being quite a handy development! I think I prefer specific placement, but random loot has its place

Re: Substantial job for a good scripter.

Posted: Sat Oct 20, 2012 2:05 pm
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.

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

Posted: Sat Oct 20, 2012 5:13 pm
by Komag
added to Editing Superthread 8-)

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

Posted: Sun Nov 04, 2012 3:12 pm
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.

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

Posted: Sun Nov 04, 2012 6:12 pm
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.

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

Posted: Sun Nov 04, 2012 6:56 pm
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...