[Mod] Random Item Stats

Cykotitan

Experienced G'
Joined
Nov 4, 2008
Messages
16,894
Best answers
4
Reaction score
818
A friendly warning: This mod most likely WON'T work on your 9.X server in case you're sticking to TFS 0.2.* aka Mystic Spirit - the required (0.3/0.4) functions are not available even if mod support was present

I got the idea from http://otland.net/f485/random-item-stats-creatures-can-drop-boosted-items-111028/ which means devianceone gets the most of credit.

Its based on my old loot ring script, modified to allow effects to be shown

Installation is straightforward, create new .xml file in /mods folder with contents below:
Lua:
<?xml version="1.0" encoding="UTF-8"?>
<mod name="Random Item Stats" enabled="1">
<config name="itemstats_conf"><![CDATA[
-- //
	extra_loot_key = 123 --: optional storage for higher loot rate
	vocation_base_attackspeed = getVocationInfo(1).attackSpeed --: used for attackSpeed stat
-- //

tiers, attr = {}, {}

tiers['rare'] = {
	color = 66, -- color of 'RARE' text
	extra = {0, 0},
	attrNames = true, -- show attribute names instead of rare
	chance = {
		[1] = 10000,
		[2] = 5000 -- chance for 2nd stat
	}
}
tiers['epic'] = {
	color = 35,
	extra = {7, 20}, -- additional percent bonus
	chance = {
		[1] = 3333,
		[2] = 25000
	}
}
tiers['legendary'] = {
	color = 149,
	extra = {20, 35},
	chance = {
		[1] = 1000,
		[2] = 100000 -- 2 bonuses always
	}
}

MELEE = 0
DISTANCE = 1
ARMOR = 2
SHIELD = 3
WAND = 4
DURATION_RING = 5
CHARGES = 6

--! attributes
attr['quick'] = {
	attr = 'attackSpeed',
	name = 'Attack Speed',
	percent = {6, 20},
	types = {MELEE, DISTANCE, WAND}
}
attr['fortified'] = {
	attr = 'extraDefense',
	base = 'defense',
	name = 'Defense',
	percent = {7, 25},
	types = {MELEE, SHIELD}
}
attr['deadly'] = {
	attr = 'extraAttack',
	base = 'attack',
	name = 'Attack',
	types = {MELEE},
	percent = {7, 25}
}
attr['strong'] = {
	attr = 'armor',
	name = 'Armor',
	percent = {7, 20},
	types = {ARMOR}
}
attr['hawkeye\'s'] = {
	attr = 'hitChance',
	name = 'Hit Chance',
	percent = {10, 25},
	types = {DISTANCE}
}
--[[ // not available without source edit
attr['farsight'] = {
	attr = 'shootRange',
	name = 'Shoot Range',
	percent = {17, 34},
	types = {DISTANCE, WAND}
}
]]
attr['charged'] = {
	attr = 'charges',
	name = 'Charges',
	percent = {30, 45},
	types = {CHARGES}
}
attr['divine'] = {
	attr = 'duration',
	name = 'Duration',
	percent = {35, 50},
	types = {DURATION_RING}
}
--/ attributes

rate = getConfigInfo('rateLoot')

if( getConfigInfo('monsterLootMessage') ~= 0 )then
	print('[Notice] Set monsterLootMessage = 0 to prevent duplicate loot messages')
end
]]></config>

<event type="kill" name="itemstats" event="script"><![CDATA[
domodlib('itemstats_conf')

function round(n, s)
	return tonumber(('%.' .. (s or 0) .. 'f'):format(n))
end

function getContentDescription(uid, sep)
	local ret, i, containers = '', 0, {}
	while( i < getContainerSize(uid) )do
		local v, s = getContainerItem(uid, i), ''
		local k = getItemInfo(v.itemid)
		k.name = getItemAttribute(v.uid, 'name') or k.name
		if( k.name ~= '' )then
			if( v.type > 1 and k.stackable and k.showCount )then
				s = v.type .. ' ' .. k.plural
			else
				local article = getItemAttribute(v.uid, 'article') or k.article
				s = (article == '' and '' or article .. ' ') .. k.name
			end
			ret = ret .. (i == 0 and not sep and '' or ', ') .. s
			if( isContainer(v.uid) and getContainerSize(v.uid) ~= 0 )then
				table.insert(containers, v.uid)
			end
		else
			ret = ret .. (i == 0 and not sep and '' or ', ') .. 'an item of type ' .. v.itemid .. ', please report it to gamemaster'
		end
		i = i + 1
	end
	for i = 1, #containers do
		ret = ret .. getContentDescription(containers[i], true)
	end
	return ret
end

local function send(cid, corpse, monster)
	if( isPlayer(cid) )then
		local ret = corpse and isContainer(corpse) and getContentDescription(corpse)
		doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'Loot of ' .. monster .. ': ' .. (ret ~= '' and ret or 'nothing'))
		local party = getPlayerParty(cid)
		if( party )then
			for _, pid in ipairs(getPartyMembers(party)) do
				doPlayerSendChannelMessage(pid, '', 'Loot of ' .. monster .. ': ' .. (ret ~= '' and ret or 'nothing'), TALKTYPE_CHANNEL_W, CHANNEL_PARTY)
			end
		end
	end
end

local function createLoot(i, ext)
	local item = type(i.id) == 'table' and i.id[math.random(#i.id)] or i.id
	local random = math.ceil(math.random(100000) / ext)
	local tmpItem, f

	if( random < i.chance )then
		if i.subType == -1 then
			f = getItemInfo(item)
		end
		tmpItem = doCreateItemEx(item,
			i.subType ~= -1 and i.subType or
			f.stackable and random % i.count + 1 or
			f.charges ~= 0 and f.charges or
			1
		)
	end

	if( not tmpItem )then
		return
	end

	if( i.actionId ~= -1 )then
		doItemSetAttribute(tmpItem, 'aid', i.actionId)
	end

	if( i.uniqueId ~= -1 )then
		doItemSetAttribute(tmpItem, 'uid', i.uniqueId)
	end

	if( i.text ~= '' )then
		doItemSetAttribute(tmpItem, 'text', i.text)
	end

	local ret, done

	for k, v in pairs(tiers) do
		local cur, used = {}, {}
		for i = 1, #v.chance do
			if( math.random(100000) <= v.chance[i] )then
				if( f )then
					f = getItemInfo(item)
				end
				if( not f.stackable )then
					for m, n in pairs(attr) do
						if( not table.find(used, m) and
						(
							( table.find(n.types, MELEE) and table.find({WEAPON_SWORD, WEAPON_CLUB, WEAPON_AXE}, f.weaponType) ) or
							( table.find(n.types, DISTANCE) and f.weaponType == WEAPON_DIST and f.ammoType ~= 0 ) or
							( table.find(n.types, ARMOR) and f.armor ~= 0 and f.wieldPosition ~= CONST_SLOT_NECKLACE ) or
							( table.find(n.types, SHIELD) and f.defense ~= 0 and f.weaponType == WEAPON_SHIELD ) or
							( table.find(n.types, WAND) and f.weaponType == WEAPON_WAND ) or
							( table.find(n.types, DURATION_RING) and f.wieldPosition == CONST_SLOT_RING and f.transformEquipTo ~= 0 ) or
							( table.find(n.types, CHARGES) and table.find({CONST_SLOT_RING, CONST_SLOT_NECKLACE}, f.wieldPosition) and f.charges ~= 0 )
						) )then
							table.insert(cur, m)
						end
					end

					if( #cur ~= 0 )then
						local n = cur[math.random(#cur)]
						table.insert(used, n)

						n = attr[n]
						local percent, new, tmp = math.random(n.percent[1] + (v.extra[1] or 0), n.percent[2] + (v.extra[2] or 0))
						-- hacks
						if( n.attr == 'duration' )then
							tmp = getItemInfo(f.transformEquipTo)
							if tmp.transformDeEquipTo ~= item then
								break
							end
							new = round( tmp.decayTime * (1 + percent / 100) * 1000 )
						elseif( n.attr == 'attackSpeed' )then
							new = round( vocation_base_attackspeed / (1 + percent / 100) )
						elseif( n.attr == 'hitChance' ) then
							new = round(
								f.hitChance == -1 and
									percent
								or 
									f.hitChance * (1 + percent / 100)
							)
						else
							new = round(
								n.base and
									f[n['attr']] + f[n['base']] * (percent / 100)
								or
									f[n['attr']] * (1 + percent / 100)
							)

							if( new == f[n[n.base and 'base' or 'attr']] )then -- no improvement
								break
							end
						end

						doItemSetAttribute(tmpItem, n.attr:lower(), new)

						local name = getItemAttribute(tmpItem, 'name')
						if( v.attrNames or not name )then
							local name = (v.attrNames and used[#used] or k) .. ' ' .. (name or f.name)
							doItemSetAttribute(tmpItem, 'name', name)

							if( f.article ~= '' )then
								local article = getArticle(name)
								if( article ~= f.article )then
									doItemSetAttribute(tmpItem, 'article', article)
								end
							end
						end

						local desc = getItemAttribute(tmpItem, 'description') or f.description
						doItemSetAttribute(tmpItem, 'description', '[' .. n.name .. ': +' .. percent .. '%]' .. (desc == '' and '' or '\n' .. desc))

						ret = k
					end
					cur = {}
					if( #v.chance == i )then
						done = true
					end
				end
			else
				done = i ~= 1
				break
			end
		end
		if( done )then
			break
		end
	end

	return tmpItem, ret
end

local function createChildLoot(parent, i, ext, pos)
	if( not i or #i == 0 )then
		return true
	end

	local size, cap = 0, getContainerCap(parent)
	for k = 1, #i do
		if( size == cap )then
			break
		end
		local tmp, ret = createLoot(i[k], ext)
		if( tmp )then
			if( isContainer(tmp) )then
				if( createChildLoot(tmp, i[k].child, ext, pos) )then
					doAddContainerItemEx(parent, tmp)
					size = size + 1
				else
					doRemoveItem(tmp)
				end
			else
				if( ret )then
					doSendMagicEffect(pos, CONST_ME_MAGIC_GREEN)
					doSendAnimatedText(pos, ret:upper(), tiers[ret].color)
				end
				doAddContainerItemEx(parent, tmp)
				size = size + 1
			end
		end
	end

	return size > 0
end

local function dropLoot(pos, v, ext, master, cid, target)
	local corpse
	if( not master or master == target )then -- 0.3/4
		corpse = getTileItemById(pos, v.lookCorpse).uid
		if( isContainer(corpse) )then
			for i = 1, getContainerSize(corpse) do
				doRemoveItem(getContainerItem(corpse, 0).uid)
			end
			local size, cap = 0, getContainerCap(corpse)
			for i = 1, #v.loot do
				if( size == cap )then
					break
				end
				local tmp, ret = createLoot(v.loot[i], ext)
				if( tmp )then
					if( isContainer(tmp) )then
						if( createChildLoot(tmp, v.loot[i].child, ext, pos) )then
							doAddContainerItemEx(corpse, tmp)
							size = size + 1
						else
							doRemoveItem(tmp)
						end
					else
						if( ret )then
							doSendMagicEffect(pos, CONST_ME_MAGIC_GREEN)
							doSendAnimatedText(pos, ret:upper(), tiers[ret].color)
						end
						doAddContainerItemEx(corpse, tmp)
						size = size + 1
					end
				end
			end
		end
	end
	send(cid, corpse, v.description)
end

function onKill(cid, target, damage, flags)
	if( (damage == true or bit.band(flags, 1) == 1) and isMonster(target) )then -- 0.3/4
		local v = getMonsterInfo(getCreatureName(target))
		if( v and v.lookCorpse ~= 0 )then
			local s = getCreatureStorage(cid, extra_loot_key)
			addEvent(dropLoot, 0, getThingPos(target), v, s == -1 and rate or s, getCreatureMaster(target), cid, target)
		end
	end
	return true
end
]]></event>

<event type="login" name="itemstats_login" event="buffer"><![CDATA[
	registerCreatureEvent(cid, 'itemstats')
]]></event>

</mod>
Post bugs and stuff here so they can get resolved asap

Resources:
 
Last edited:

VirrageS

←•†ĿuĀ && ©¤¤•→
Joined
May 30, 2010
Messages
984
Best answers
0
Reaction score
59
Location
Poland
Very nice master. ^_^

But what if someone use yours loot ring and also want to use it??
 

Summ

(\/)(;,,;)(\/) Y not?
Staff member
Global Moderator
Joined
Oct 15, 2008
Messages
4,160
Best answers
9
Reaction score
1,010
Location
Germany :O
Nice release. To bad I already made this half a year for own purposes :/

=> You must spread some Reputation around before giving it to Cykotitan again.
 
OP
Cykotitan

Cykotitan

Experienced G'
Joined
Nov 4, 2008
Messages
16,894
Best answers
4
Reaction score
818
be careful when adding it (don't add it mindlessly) and take some time to tweak the percent or chances before enabling the mod because the values i provided probably aren't ideal for every server
!!
 

Exedion

Active Member
Joined
Jun 11, 2007
Messages
628
Best answers
0
Reaction score
28
Is rare see scripts releases from cyko, the end of the world is near! thanks cyko :)
 

God Nixez

Member
Joined
Sep 20, 2009
Messages
399
Best answers
0
Reaction score
16
works i guess ;d btw where have u been ;o
 

Kudzu

Active Member
Joined
Apr 9, 2008
Messages
512
Best answers
0
Reaction score
38
Location
localhost
Cool! Testing

Edit: 0.4 DEV a lot of errors,spam ;s
 
Last edited:

God Nixez

Member
Joined
Sep 20, 2009
Messages
399
Best answers
0
Reaction score
16
i tested it on 0.4 rev 3777 and it works as a knife in a cake 100% stable no crashes no bugs no errors ~
 
Top