Object resolvers update
Summary: This updates object resolvers, eliminating redundant code to ensure consistent behaviour and integrating them into the debugging object creation interface -- allowing resolver filters to be debugged directly within the debugging dialogs.
It adds a new system for NPCs choosing equipment to wear based on filters, and updates ToME character class descriptors and Entity definitions to ensure only appropriate equipment is worn (to prevent wearing Objects that interfere with talents). This fixes the issue of NPCs getting added random classes with many unusable talents. (Embers and Ashes class descriptors and Entities work as before, but need to be updated to benefit from the changes.)
Details:
New utility functions (useful for debugging): string.fromFunction(fct, fmt) returns a string abbreviation for a function, including filepath, line numbers
string.fromValue(v, recurse, offset, prefix, suffix) -- enhancement of tostring function with more thorough handling of tables and functions
string.fromTable(src, recurse, offset, prefix, suffix, key_recurse): returns a single-line string representation of a table, including string representations of subtables and functions. The recursion level for both keys and values can be set. The converted string is lua source code compatible, depending on arguments.
engine.Zone:checkFilter: Added documentation, can set filter.allow_uniques to allow unique items to pass the filter, special function takes the filter as its 2nd argument
engine.Zone:filterToString: updated -- handles subtables, functions, and other complex values
engine.Zone:makeEntity: the type parameter can be a list of entities
engine.Zone:makeEntityByName: Added documentation, type can be a string descriptor used to load a base list (follows makeEntity filter.base_list)
engine.Entity:resolve: Added documentation
engine.Birther: Bugfix: the order of resolvers is preserved when applying birth descriptors (causes resolver.attachtinker to fail with no equipment to attach to)
engine.interface.ActorInventory:initBody() May be reapplied to an actor without overwriting existing inventories (merges properties), copies the inventory definition short_name
dialogs.debug.SummonCreature, dialogs.debug.RandomActor: Actors are fully resolved before added: including "addedToLevel", "on_added", "on_added_to_level" (except spawning escorts) updating for game difficulty, etc. on generation, before adding Uniques (including inventory items) are not registered in the game until the actor is actually placed.
dialogs.debug:RandomObject: Can set the working actor to be a NPC (defaults to the player) New button/hotkeys to open the working actor character sheet and inventory screens Can select from one of several object resolvers, which will call the appropriate code and resolve the object. This works like other object generation, allowing preview/inspection before adding the object to the game, which is controlled by the selected resolver as normal Uniques created are not registered until actually added to the game.
CharacterSheet: can always open the inventory dialog in debug mode
Actor:resolveLevelTalents: code to apply self.learn_tids moved from createRandomBoss to this function (enabling this function for all Actors)
New function Object:wornLocations(use_actor, weight_fn, filter_field, no_type_check) (This functionality replaces the no_npc_weapon_equip flag.) Returns a table if inventory locations where an object can be worn, filtered and sorted The "best" locations appear first, permitting Actors to automatically optimize their equipment. The default criteria are simple (based on object "power", from material level and getPowerRank), but can be updated to be more sophisticated.
New function Actor:wearAllInventory(force, ...) Attempts to wear all items in main inventory, using Object:wornLocations to find the best locations (currently does not attempt to attach tinkers, aggressively remove worn items, or support psionic focus)
New function Actor:updateObjectRequirements(o) Returns object requirements modified by actor talents Used by Actor:canWearObject, Object:getRequirementDesc, and resolvers.resolveObject
New function Actor:searchAllInventories(o, fct) Searches all inventories for an object (including attached tinkers). Triggers an optional callback on found items.
NPC:addedToLevel: classes added from higher difficulty settings allow equipment to be updated and autolevel stats (if more than one class added) calls Actor:wearAllInventory to equip objects
game.state:entityFilterAlter: Added documentation, updated RandomObject help
game.state:entityFilter: Added documentation, updated RandomObject help
game.state:entityFilterPost: Added documentation, updated RandomObject and RandomActor help
game.state:egoFilter: Added documentation
game.state:applyRandomClass: Fixed a bug causing starting class stats to not be added properly Copies all class resolvers with _allow_random_boss set true (resolvers.equip: ignore_material_restriction=true) Resolvers enabled: inscription, inscriptions, auto_equip_filters, attach_tinker, inventory, drops, drop_randart The can_tinker table is copied (allows resolvers.attach_tinker to work properly) autolevel set to "random_boss" unless data.autolevel == false May set data.update_body to force adding inventory slots to the actor May set data.spend_points to spend all unspent stat points data.force_classes can take numerical indexes. Fixed a bug causing multiple forced classes to be applied in definition order rather than randomly. (Example: {"Rogue", Alchemist=true, Necromancer=true} always adds the Rogue class first then Alchemist and Necromancer in random order, before adding any other random classes)
game.state:createRandomBoss: Allows learn_tids to be applied by resolveLevelTalents (random class talents are learned before attempting to wear equipment with NPC:addedToLevel) No longer removes equip and drops resolvers from the base NPC This preserves important drops and equipment (updated to "boss" levels) required for NPC talents (i.e. randelite Naga Mymidons are not spawned disarmed/improperly armed with certain classes) -- applyRandomClass replaces equipment and updates inventory filters as needed.
New resolver -- resolvers.auto_equip_filters(t, readonly): Sets filters by inventory slot to be checked before automatically wearing objects. Can be used by class descriptors and general npc definitions to ensure that equipment automatically equipped will not interfere with talents. Input is either a table of filters or a class descriptor name to load the filters from the referenced birth descriptors (i.e. resolvers.auto_equip_filters("Archmage")). As usual, multiple copies of this resolver overwrite each other by inventory slot (use readonly to prevent), so randbosses with more than one class will generally use the filters for the last class applied. (i.e. Alchemist (applied first), Rogue (applied second) will preferentially equip daggers in MAINHAND/OFFHAND and alchemist gems in QUIVER)
New general object resolver function -- resolvers.resolveObject(e, filter, do_wear, tries): Called by all filter-based object resolvers for consistency, interprets the filter fields: base_list, random_art_replace, alter (takes the resolving actor as an argument), autoreq (can trigger actor levelup and learning talents in addition to stats), force_inven, force_item, force_drop, never_drop, _use_object (sets a pre-resolved object to use, skipping random generation), replace_unique (filter for generating a replacement for a specific object) Uses Object:wornLocations to find the best locations in which to wear objects. Correctly handles replaced objects and always places unworn objects into main inventory unless explicitly forbidden. (Calls game.zone:addObject only for objects that are actually added to the actor.) Documentation serves as core help in RandomObject dialog for resolvers.
resolvers.equip: updated to use resolvers.resolveObject
resolvers.inventory: updated to use resolvers.resolveObject
resolvers.drops: updated to use resolvers.resolveObject
resolvers.attachtinker: updated to use resolvers.resolveObject, will place the tinker in main inventory if .keep_object is set
resolvers.drop_randart: interprets .data field as input to game.state:generateRandart, may set ._use_object to use a pre-resolved object and .no_add to return the generated object instead of adding it to inventory
Class Descriptors updated with inventory filters: Spellpower based casters (Anorithil, Paradox Mage, Archmage, Alchemist, Necromancer, Corruptor): MAINHAND: staff OFFHAND: none unless a 1H weapon is equipped in MAINHAND Alchemist: QUIVER: alchemist-gem
Oozemancer, Summoner, Doomed: MAINHAND/OFFHAND: mindstars
Stone Warden: MAINHAND/OFFHAND: shield or weapon with the block combat field
Brawler: MAINHAND/OFFHAND: unarmed
Reaver: MAINHAND/OFFHAND: 1H weapon
Archer: MAINHAND: Archery weapon QUIVER: Ammo must match type of MAINHAND weapon
Temporal Warden: MAINHAND: longbow OFFHAND: none QUIVER: Ammo must match type of MAINHAND weapon QS_MAINHAND: 1H weapon
Sun Paladin, Bullwark: MAINHAND: 1H weapon OFFHAND: shield or weapon with the block combat field BODY: heavy or massive armor
Berzerker: MAINHAND: 2H weapon OFFHAND: unarmed
Rogue, Shadowblade, Marauder: MAINHAND/OFFHAND: dagger Rogue, Shadowblade: BODY: light or cloth armor
Skirmisher: MAINHAND: sling OFFHAND: shield or weapon with the block combat field QUIVER: Ammo must match type of MAINHAND weapon
Some NPC Entity updates: Bosses in Trollmire, The Maze, Daikara, Dreadfell, Elven Ruins, High Peak, Orc Prides, Assassin Lord, Tannen, Golbug, Corrupted Oozemancer, Some major and minor demons, various caster npcs, many orcs, thieves, minotaurs, yaechs, naga