Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: Item Syndrome Reborn  (Read 3487 times)

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Item Syndrome Reborn
« on: July 13, 2015, 12:20:32 pm »

Item syndrome for 40.x, I used syndrome-util (which comes with DFHack) so this should be more-or-less future proof.

Like the old item syndrome this is a simple command, so just place it in "raw/scripts" and start it in your onload.init file.

Code: [Select]
-- Applies syndromes to creatures from inventory items.

--
-- Item Syndrome Reborn is copyright 2015 by Milo Christiansen
--
-- Loosely based on the original Item Syndrome (by Putnam).
--
-- This software is provided 'as-is', without any express or implied warranty. In
-- no event will the authors be held liable for any damages arising from the use of
-- this software.
--
-- Permission is granted to anyone to use this software for any purpose, including
-- commercial applications, and to alter it and redistribute it freely, subject to
-- the following restrictions:
--
-- 1. The origin of this software must not be misrepresented; you must not claim
-- that you wrote the original software. If you use this software in a product, an
-- acknowledgment in the product documentation would be appreciated but is not
-- required.
--
-- 2. Altered source versions must be plainly marked as such, and must not be
-- misrepresented as being the original software.
--
-- 3. This notice may not be removed or altered from any source distribution.
--

--
-- Item Syndrome Reborn is a complete rewrite of the old Item Syndrome script.
-- Functionally it should be more-or-less the same, but more efficient and
-- generally improved.
--
-- Some features of the old item syndrome are not available (for example transformation
-- reequips), but all the important stuff is present.
--
-- In general what worked with the old script should work with this one, with a
-- few minor changes.
--
-- The biggest change is that if you want to apply a syndrome based on item type
-- instead of looking the syndrome up by item name (for example "battle axe") the
-- syndrome is looked up by item subtype ID ("ITEM_WEAPON_AXE_BATTLE"). This is done
-- to improve precision. Also such syndromes can be in any material, anywhere in the
-- raws, not just a material with a special name.
--
-- The flags have also been redone, they are now all permissions instead of a mixture
-- of permissions and restrictions.
--
-- Instructions:
--
-- Item Syndrome Reborn can apply syndromes in three ways:
--
-- * If the item's material has a syndrome with the class "DFHACK_ITEM_SYNDROME"
--   It will apply the syndrome to the creature subject to the flags (discussed later)
--   and any immunities/vulnerabilities the syndrome may have.
--
-- * All syndromes in every material in the entire world are searched for one that has
--   a name (the "SYN_NAME" tag) that matches the item's subtype ID *and* the "DFHACK_ITEM_SYNDROME"
--   class. Obviously this only works with items that have subtypes (armor, weapons, ammo, etc).
--
-- * If the item has any contaminants the contaminants are checked for valid syndromes
--   (syndromes with the "DFHACK_ITEM_SYNDROME" class) and if any are found they are
--   applied subject to the flags (discussed later) and any immunities/vulnerabilities
--   the syndrome may have.
--
-- Syndromes may have additional flags that modify how the syndrome is applied. These
-- flags take the form of syndrome classes (the "SYN_CLASS" tag).
--
-- * "DFHACK_AFFECTS_HAULER": Can effect units hauling the item.
-- * "DFHACK_AFFECTS_WIELDER": Can effect units that are using the item as a weapon.
-- * "DFHACK_AFFECTS_WEARER": Can effect unit that are wearing the item as armor.
-- * "DFHACK_AFFECTS_STUCKIN": Effects units the item has stuck in.
-- * "DFHACK_DO_NOT_REMOVE": Effect is not removed when the item is removed.
--

local synutil = require 'syndrome-util'
local eventful = require 'plugins.eventful'

-- I cache the syndrome data objects to avoid needing to regenerate them every time they are needed.
Chache = Chache or {}
Chache.Syndromes = Chache.Syndromes or {}
Chache.Items = Chache.Items or {}
dfhack.onStateChange.ItemSyndromeReborn = function(code)
-- If anything changes (or may have changed) invalidate the cache.
-- It is better to rebuild the cache unnecessarily than it is to have
-- a cache with invalid data.
Chache.Syndromes = {}
Chache.Items = {}
end

-- Does the syndrome have the "DFHACK_ITEM_SYNDROME" class?
local function isItemSyndrome(syndrome)
for _,v in ipairs(syndrome.syn_class) do
if v.value == "DFHACK_ITEM_SYNDROME" then
return true
end
end
return false
end

-- Create a new syndrome data table from a syndrome.
local function createSynDat(syndrome)
local syndat = {
syndrome = syndrome,
flags = {
AFFECTS_HAULER = false,
AFFECTS_WIELDER = false,
AFFECTS_WEARER = false,
AFFECTS_STUCKIN = false,
DO_NOT_REMOVE = false
},
}

for _, v in ipairs(syndat.syndrome.syn_class) do
local flag = string.match(v.value, "DFHACK_(.+)")
if syndat.flags[flag] ~= nil then
syndat.flags[flag] = true
end
end

return syndat
end

-- This gets a table of syndromes from the object's material and (if is_item is true)
-- the object's item subtype if the item has a subtype.
--
-- Return nil if no valid syndromes could be found, otherwise returns a table of syndrome data tables.
local function getObjectSyndromes(object, is_item)
local syndromes = {}

-- Look for syndromes attached to the material.
local mat = dfhack.matinfo.decode(object)
if mat ~= nil and mat.material ~= nil and mat.material.syndrome ~= nil then
for _, syn in ipairs(mat.material.syndrome) do
if Chache.Syndromes[syn.id] == nil then
if isItemSyndrome(syn) then
Chache.Syndromes[syn.id] = createSynDat(syn)
else
Chache.Syndromes[syn.id] = false
end
end

if type(Chache.Syndromes[syn.id]) == "table" then
table.insert(syndromes, Chache.Syndromes[syn.id])
end
end
end

-- Look for a syndrome attached to the item.
-- The syndrome should have a syndrome name that matches the item's subtype id (this only
-- works if the item has a subtype obviously).
-- Unlike the old itemSyndrome I use the item subtype ID not the item subtype name!
if is_item and object:getSubtype() ~= -1 and type(object.subtype) ~= "number" then
-- It should be possible to just get the syndromes from the cache every time except the first.
if Chache.Items[object.subtype.id] ~= nil then
for _, syndat in ipairs(Chache.Items[object.subtype.id]) do
table.insert(syndromes, syndat)
end
else
local cache = {}
for _, syn in ipairs(df.global.world.raws.syndromes.all) do
if syn.syn_name == object.subtype.id then
if isItemSyndrome(syn) then
local syndat = createSynDat(syn)
table.insert(syndromes, syndat)
table.insert(cache, syndat)
end
end
end
Chache.Items[object.subtype.id] = cache
end
end

-- No valid syndromes found.
if #syndromes == 0 then
return nil
end
return syndromes
end

-- Adds a syndrome to the unit.
local function applySyndrome(syndat, unit)
print("Item Syndrome Reborn: Applying syndrome: ("..syndat.syndrome.id..") to unit ("..unit.id..")")

synutil.infectWithSyndromeIfValidTarget(unit, syndat.syndrome, synutil.ResetPolicy.ResetDuration)
end

-- Removes a syndrome from the unit.
local function removeSyndrome(syndat, unit)
print("Item Syndrome Reborn: Removing syndrome: ("..syndat.syndrome.id..") from unit ("..unit.id..")")

synutil.eraseSyndromes(unit, syndat.syndrome.id)
end

-- Removes all item syndromes from all units (even syndromes with the DO_NOT_REMOVE flag).
local function killSyndromes()
for _, unit in ipairs(df.global.world.units.all) do
synutil.eraseSyndromeClass(unit, "DFHACK_ITEM_SYNDROME")
end
end

-- Is the item in the correct part of the units inventory for the syndrome to apply?
-- Basically makes sure the item inventory mode matches the syndrome flags.
local function itemInValidPosition(item_inv, syndat)
return (item_inv ~= nil) and -- Item taken off
((item_inv.mode == 0 and syndat.flags.AFFECTS_HAULER) or -- Item is hauled
(item_inv.mode == 1 and syndat.flags.AFFECTS_WIELDER) or -- Item is wielded
(item_inv.mode == 2 and syndat.flags.AFFECTS_WEARER) or -- Item is worn (clothing)
(item_inv.mode == 7 and syndat.flags.AFFECTS_STUCKIN)) -- Item is stuckin
end

local function itemInventorySlot(unit, item)
for inv_id, item_inv in ipairs(unit.inventory) do
if item_inv.item.id == item.id then
return item_inv
end
end
return nil
end

local function applyItemSyndromes(unit, item)
local item_inv = itemInventorySlot(unit, item)

-- First handle the item directly
local syndats = getObjectSyndromes(item, true)
if syndats ~= nil then
for _, syndat in ipairs(syndats) do
if itemInValidPosition(item_inv, syndat) then
applySyndrome(syndat, unit)
else
if not syndat.flags.DO_NOT_REMOVE then
removeSyndrome(syndat, unit)
end
end
end
end

-- Then handle any item contaminants.
if item.contaminants then
for _, contaminant in ipairs(item.contaminants) do
local syndats = getObjectSyndromes(contaminant, false)
if syndats ~= nil then
for _, syndat in ipairs(syndats) do
if itemInValidPosition(item_inv, syndat) then
applySyndrome(syndat, unit)
else
if not syndat.flags.DO_NOT_REMOVE then
removeSyndrome(syndat, unit)
end
end
end
end
end
end
end

local function handleInvChange(unit_id, item_id, old_equip, new_equip)
local item = df.item.find(item_id)
    if not item then
return
end
    local unit = df.unit.find(unit_id)
    if unit.flags1.dead then
return
end

applyItemSyndromes(unit, item)
end

local function applyAllItemSyndromes()
for _, unit in ipairs(df.global.world.units.all) do
for _, item_inv in ipairs(unit.inventory) do
-- First handle the item directly
local syndats = getObjectSyndromes(item_inv.item, true)
if syndats ~= nil then
for _, syndat in ipairs(syndats) do
if itemInValidPosition(item_inv, syndat) then
applySyndrome(syndat, unit)
else
if not syndat.flags.DO_NOT_REMOVE then
removeSyndrome(syndat, unit)
end
end
end
end

-- Then handle any item contaminants.
if item_inv.item.contaminants then
for _, contaminant in ipairs(item_inv.item.contaminants) do
local syndats = getObjectSyndromes(contaminant, false)
if syndats ~= nil then
for _, syndat in ipairs(syndats) do
if itemInValidPosition(item_inv, syndat) then
applySyndrome(syndat, unit)
else
if not syndat.flags.DO_NOT_REMOVE then
removeSyndrome(syndat, unit)
end
end
end
end
end
end
end
end
end

if ... == "enable" then
print('Item Syndrome Reborn: Enabling.')
eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE, 5)
eventful.onInventoryChange.ItemSyndromeReborn = handleInvChange

elseif ... == "disable" then
print('Item Syndrome Reborn: Disabling.')
eventful.onInventoryChange.ItemSyndromeReborn = nil

elseif ... == "kill" then
print('Item Syndrome Reborn: Killing.')
eventful.onInventoryChange.ItemSyndromeReborn = nil
dfhack.onStateChange.ItemSyndromeReborn = nil
Chache.Syndromes = {}
Chache.Items = {}

if dfhack.isWorldLoaded() then
killSyndromes()
end

elseif ... == "refresh" then
print('Item Syndrome Reborn: Refreshing.')
if dfhack.isWorldLoaded() then
killSyndromes()
applyAllItemSyndromes()
else
qerror("  World is not loaded!")
end

elseif ... == "clear_cache" then
print('Item Syndrome Reborn: Clearing the cache.')
Chache.Syndromes = {}
Chache.Items = {}

else
print([==[
Item Syndrome Reborn: Usage:

itemSyndromReborn enable
    Start periodically updating.

itemSyndromReborn disable
    Stop periodically updating.
    This does not clear the syndrome data cache!

itemSyndromReborn kill
    Stop periodically updating and remove all item syndromes from all units.
    Also clears the syndrome data cache.

itemSyndromReborn refresh
    Removes all item syndromes and the runs a single update.
    Use to fix "stuck" syndromes (should never happen, but just in case).

itemSyndromReborn clear_cache
    Clear the syndrome data cache.
]==])
end
« Last Edit: September 05, 2015, 11:03:05 am by milo christiansen »
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Roses

  • Bay Watcher
    • View Profile
Re: Item Syndrome Reborn
« Reply #1 on: July 13, 2015, 12:55:58 pm »

How is this different from modtools/item-trigger?
Logged

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Item Syndrome Reborn
« Reply #2 on: July 13, 2015, 01:03:21 pm »

Last time I tried it item-trigger was broken, plus this has finer control (since it only does syndromes).

If item trigger works now then this is less useful, but it is still easier to use for some cases.

EDIT: item-trigger isn't the only thing that is broken (and I have reported broken in the DFHack thread) that people seem to blithely assume to be working.
« Last Edit: July 13, 2015, 01:05:16 pm by milo christiansen »
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Roses

  • Bay Watcher
    • View Profile
Re: Item Syndrome Reborn
« Reply #3 on: July 13, 2015, 02:12:18 pm »

item-trigger was working for me the last time I checked it, but there was an issue with adding syndromes using the built in tools (modtools/add-syndrome which calls syndrome-util.infectWithSyndrome) which I see you are using as well. Have you double checked that the syndrome is being correctly applied? Syndromes which transform a unit or add a name have worked for me, but attempting to add syndromes with things like CE_BLEEDING and the like, don't seem to work, most likely because no wound is being applied.

As for item-trigger, I just tested it again and it successfully printed the arguments when I equipped an item.
Logged

expwnent

  • Bay Watcher
    • View Profile
Re: Item Syndrome Reborn
« Reply #4 on: July 15, 2015, 02:40:01 am »

Posting to monitor item-trigger error report.
Logged

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Item Syndrome Reborn
« Reply #5 on: July 18, 2015, 10:31:43 am »

Well last time I tried item-trigger it printed continuous error spam to the DFHack console, but it may have been fixed, I'll try it again (I assumed it was still broken because there was no mention of it in the changelog). (last time I tried it was shortly after the bug it had that would make DF CTD was fixed)

I assume syndrome-util is working, but the only effects I have applied are stat+ effects for power armor, so other things may be broken.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Atomic Chicken

  • Bay Watcher
    • View Profile
Re: Item Syndrome Reborn
« Reply #6 on: July 28, 2015, 05:15:37 am »

Posting to monitor item-trigger error report.
continuous error spam
expwnent, I too have had issues with item-trigger:

1. The following error message constantly spams the console whenever a material is defined as the trigger:
Spoiler (click to show/hide)

2. item-trigger seemingly does not work with items lacking a subtype, such as rings, despite them being made of the material defined as a trigger. Although this is not a major problem, I find it inhibiting, as magical jewelry in particular is often a common theme which modders may wish to use. Itemsyndrome, on the other hand, appears to work with any item made of the specified material.

Oh, by the way, do you ever plan on extending the script so that more than one condition (such as material+contaminant) can be defined, such that the script is only activated when both are present? Because that would be fantastic. (The code refers to it as a work in progress).



milo christiansen, I'm afraid that I'm also having issues with itemsyndrome. Is the syndrome supposed to be removed once the item is unequipped? In my tests, it appeared to remain on the unit, and the following error message was received:
Spoiler (click to show/hide)


On that note, do you think it would be possible to add an option for making the syndrome permanent in that it remains after the item is unequipped? Such an option would be useful for making "cursed" items, for example.

Note that all of my testing for both scripts was carried out in adventurer mode, rather than in arena mode.
Logged
As mentioned in the previous turn, the most exciting field of battle this year will be in the Arstotzkan capitol, with plenty of close-quarter fighting and siege warfare.  Arstotzka, accordingly, spent their design phase developing a high-altitude tactical bomber. 

Deon

  • Bay Watcher
  • 💀 💀 💀 💀 💀
    • View Profile
Re: Item Syndrome Reborn
« Reply #7 on: July 28, 2015, 05:33:29 am »

This is a really useful script, I will start using it as soon as I get to mechanized armor, flamethrowers and hazmat suits. I hope that issue AC reported above with "stuck" syndromes can be fixed without causing too much trouble for you.
Logged
▬(ஜ۩۞۩ஜ)▬
✫ DF Wanderer ✫ - the adventure mode crafting and tweaks
✫ Cartographer's Lounge ✫ - a custom worldgen repository

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Item Syndrome Reborn
« Reply #8 on: July 30, 2015, 02:35:52 pm »

I'll have to look into that error, I didn't get that with my power armor test, but that was hardly exhaustive.

Try "itemSyndromeReborn refresh" and see if that clears your stuck syndrome, if not then it may be a problem with syndrome-util (or with my script of course).

Hopefully I'll have a fix for you next time I get online.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Item Syndrome Reborn
« Reply #9 on: August 05, 2015, 01:33:06 pm »

Fixed the known bugs (and did some more testing, although it is still less than exhaustive).

Removing syndromes with the item was broken, and the syndrome reset policy was invalid.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

Atomic Chicken

  • Bay Watcher
    • View Profile
Re: Item Syndrome Reborn
« Reply #10 on: September 04, 2015, 07:31:46 am »

Fixed the known bugs (and did some more testing, although it is still less than exhaustive).

Removing syndromes with the item was broken, and the syndrome reset policy was invalid.
I've been meaning to test the fix for quite a while now, but I tried it out today and it appears to work fine. Thanks for adding the DFHACK_DO_NOT_REMOVE option!
Logged
As mentioned in the previous turn, the most exciting field of battle this year will be in the Arstotzkan capitol, with plenty of close-quarter fighting and siege warfare.  Arstotzka, accordingly, spent their design phase developing a high-altitude tactical bomber. 

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: Item Syndrome Reborn
« Reply #11 on: September 05, 2015, 11:01:32 am »

Thank you!

I did discover another bug. When plant gathering is going on the script spams the console with errors. It doesn't hurt anything, but the fix is simple (I'll have it fixed in a second).
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS