Cykotitan
Experienced G'
- Joined
- Nov 4, 2008
- Messages
- 16,892
- Solutions
- 5
- Reaction score
- 851
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:
Post bugs and stuff here so they can get resolved asap
Resources:
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>
Resources:
Last edited: