diff --git a/aard_inventory.changelog b/aard_inventory.changelog index d860b99..302a217 100644 --- a/aard_inventory.changelog +++ b/aard_inventory.changelog @@ -2,6 +2,24 @@ dbot.changelog = {} +dbot.changelog[2.0019] = +{ + { change = drlDbotChangeLogTypeNew, + desc = [[Added "dinv weapon ..." mode that supports swapping weapons with specific damage types]] + }, + { change = drlDbotChangeLogTypeFix, + desc = [[Fixed bug that allowed heroonly items in non-hero equipment sets]] + }, + { change = drlDbotChangeLogTypeFix, + desc = [[Fixed conflict between searching in "id" mode and using an "id" query. The search + mode that shows object IDs is now named "objid" to differentiate it from a query + searching for a specific ID.]] + }, + { change = drlDbotChangeLogTypeMisc, + desc = [[Updated built-in priorities]] + } +} + dbot.changelog[2.0018] = { { change = drlDbotChangeLogTypeFix, diff --git a/aard_inventory.xml b/aard_inventory.xml index 4666f0c..17e1700 100644 --- a/aard_inventory.xml +++ b/aard_inventory.xml @@ -43,6 +43,7 @@ inv.cache : Item cache management and access inv.priority : How to prioritize / weight each stat for equipment sets inv.score : Scoring items and sets based on an equipment set prioritization inv.set : Equipment set management creation and management +inv.weapon : Weapon-only equipment sets, typically based on damage type(s) inv.snapshot : Snapshots of specific equipment sets inv.statBonus : Calculation and storage of character bonuses due to spells and equipment inv.analyze : Calculation and storage of a priority's "optimal" equipment sets at each level @@ -124,7 +125,7 @@ Usage Inventory table access dinv build confirm dinv refresh [on | off | eager | all] - dinv search [id | full] + dinv search [objid | full] Item management dinv get @@ -135,6 +136,7 @@ Usage Equipment sets dinv set [display | wear] + dinv weapon [next | ] dinv snapshot [create | delete | list | display | wear] dinv priority [list | display | create | clone | delete | edit | copy | paste | compare] @@ -274,7 +276,7 @@ Feature Wishlist + + + + + + @w") + dbot.print("@W " .. pluginNameCmd .. " search @Y[objid | full] @G@w") end -- inv.cli.search.usage @@ -1842,8 +1868,8 @@ its name. Also, queries will accept "key" instead of "keywords", "loc" instead and "rloc" instead of "rlocation". Yeah, I'm lazy sometimes... Performing a search will display relevant information about the items whose characteristics match -the query. There are three modes of searches: "@Cbasic@W", "@Cid@W", and "@Cfull@W". A basic search displays -just basic information about the items -- surprise! An id search shows everything in the basic +the query. There are three modes of searches: "@Cbasic@W", "@Cobjid@W", and "@Cfull@W". A basic search displays +just basic information about the items -- surprise! An objid search shows everything in the basic search in addition to the item's unique ID. A full search shows lots of info for each item and is very verbose. @@ -1860,7 +1886,7 @@ Examples: @W 40@w @YDagger of @RAardwolf@w @Wdagger @G100@W @G 10@W @G 5@W @G 5@W Mental sharp 0 0 0 0 0 0 @W 2) Show unique IDs and info for all level 91 ear and neck items - "@Gdinv search id wearable ear level 91 || wearable neck level 91@W" + "@Gdinv search objid wearable ear level 91 || wearable neck level 91@W" @WLvl Name of Armor Type HR DR Int Wis Lck Str Dex Con Res HitP Mana Move @W 91@w @c:@C:@W:@CSt@cerling @G(1851993170) @Wear 0 @G 12@W @G 4@W 0 @G 3@W 0 0 0 @G 9@W 0 @G 30@W @R -60 @@ -2436,6 +2462,105 @@ Examples: end -- inv.cli.set.examples +inv.cli.weapon = {} +function inv.cli.weapon.fn(name, line, wildcards) + local priority = wildcards[1] or "" + local damTypes = wildcards[2] or "" + local endTag = inv.tags.new(line) + + if (not inv.init.initializedActive) then + dbot.info("Skipping weapon request: plugin is not yet initialized (are you AFK or sleeping?)") + return inv.tags.stop(invTagsSet, endTag, DRL_RET_UNINITIALIZED) + elseif dbot.gmcp.statePreventsActions() then + dbot.info("Skipping weapon request: character's state does not allow actions") + return inv.tags.stop(invTagsSet, endTag, DRL_RET_NOT_ACTIVE) + end -- if + + if (priority == "next") then + inv.weapon.next(endTag) + else + inv.weapon.use(priority, damTypes, endTag) + end -- if + +end -- inv.cli.weapon.fn + + +function inv.cli.weapon.usage() + dbot.print("@W " .. pluginNameCmd .. " weapon @G[next | ]@w") +end -- inv.cli.weapon.usage() + + +function inv.cli.weapon.examples() + dbot.print("@W\nUsage:\n") + inv.cli.weapon.usage() + dbot.print( +[[@W +Equipment sets can specify which damage types are allowed on weapons in the set. However, +it would be tedious to create multiple priorities to target specific damage types. The +"@Cweapon@W" mode provides a simple and convenient way to indicate which damage types you +want to use (or do *not* want to use) as an extension to an existing priority. See the +helpfile at "@Gdinv help priority@W" for details on how to create and use a priority. + +The first step is to specify which damage types you want to allow within a particular +priority by providing a list of specific damage types (e.g., "pierce" or "mental") and/or +damage type groups (e.g., "all", "physical", or "magic"). The plugin will determine the +optimal equipment set (including weapons) that matches the damage type requirements and +then wear the items in that equipment set. + +Once the weapon damage types are specified, you may use the "@Gdinv weapon next@W" command +to rotate through the types. Each time the "next" command is given the plugin will remove +one of the available damage types from the originally provided list and generate and wear +the best possible equipment set that is compatible with the remaining damage types. The +plugin's algorithm starts with highest scoring equipment set and then removes the damage +type of that set's primary weapon to find the next best equipment set. + +In most cases, using the "@Cweapon@W" mode will just swap your weapons (and possibly your +shield and/or held item if that's your priority's preference). However, it is possible that +the optimal equipment set for a particular weapon also involves changing a few other pieces +of equipment. Remember that this plugin always looks at an entire set's score to gauge how +good a set is and the items in the "best" set will vary over time depending on your spellup. + +Examples! + 1) Oh noes! You are at The Demon's Flight and you need a weapon that does pierce damage. + You are using the psi-melee priority (because you are awesome enough to be a psi) and + you need to swap weapons -- quick! + @Gdinv weapon psi-melee pierce@W + + 2) You are pupping at the Earth Lords and run into one of those annoying void warriors + that are immune to all magical damage. Let's tell the plugin to only use weapons with + physical damage types. Because I'm lazy, and "physical" is such a long word *grin* + you can also abbreviate it to "phys" in the example below. + @Gdinv weapon psi-melee physical@W + + 3) Same as the above example, but you are fighting a mob immune to physical damage + @Gdinv weapon psi-melee magic@W + + 4) You are fighting a mob that seems to be immune to everything. Let's try to find a + weapon that will work for you. + @Gdinv weapon psi-melee all@W + + If the first weapon set works, great :) If not, eliminate the damage type found on + your primary weapon, create a set with the remaining damage types, and try again. + @Gdinv weapon next@W + + You can keep calling @Gdinv weapon next@W" until you run out of possible weapon set + combinations. At that point, you may want to flee :p + + 5) You are a 1337 PK-er and are fighting your arch-enemy clan composed of vampires, + eldar, and giants. You want to target their vulnerabilities so you turn to dinv. + @Gdinv weapon psi-melee light slash mental@W + + 6) You are fighting a mob that you think is vulnerable to shadow, poison, and all physical + damage types. Ok, that isn't really a realistic scenario but I'm a little too tired to + make all these examples realistic tonight... + @Gdinv weapon psi-melee phys shadow poison@W + @Gdinv weapon next@W + +]]) + +end -- inv.cli.weapon.examples + + inv.cli.priority = {} function inv.cli.priority.fn(name, line, wildcards) local command = Trim(wildcards[1] or "") @@ -2467,7 +2592,7 @@ function inv.cli.priority.fn(name, line, wildcards) inv.priority.delete(priorityName1, endTag) elseif (command == "clone") then - inv.priority.clone(priorityName1, priorityName2, endTag) + inv.priority.clone(priorityName1, priorityName2, true, endTag) elseif (command == "copy") then inv.priority.copy(priorityName1, endTag) @@ -4242,7 +4367,7 @@ Examples: "@Gdinv consume add mana nachos@W" 2) Remove "nachos" from the "mana" consumable type table - "@Gdinv consume remove mana nachos@W") + "@Gdinv consume remove mana nachos@W" 3) Remove the consumable type "mana" and everything that is that type "@Gdinv consume remove mana@W" @@ -7788,7 +7913,7 @@ end -- inv.items.convertSetupFn invDisplayVerbosityBasic = "basic" -- default mode -invDisplayVerbosityId = "id" +invDisplayVerbosityId = "objid" invDisplayVerbosityFull = "full" invDisplayVerbosityDiffAdd = "diffAdd" -- internal only (shows diff format for replaced items) invDisplayVerbosityDiffRemove = "diffRemove" -- internal only (shows diff format for replaced items) @@ -10739,7 +10864,7 @@ end -- inv.cache.clearOld -- inv.priority.reset() -- -- inv.priority.create(priorityName, endTag) --- inv.priority.clone(origPriorityName, clonedPriorityName, endTag) +-- inv.priority.clone(origPriorityName, clonedPriorityName, useVerbose, endTag) -- inv.priority.delete(priorityName, endTag) -- -- inv.priority.list(endTag) @@ -10760,6 +10885,7 @@ end -- inv.cache.clearOld -- inv.priority.tableToString(priorityTable, doDisplayUnused, doDisplayColors, doDisplayDesc) -- inv.priority.stringToTable(priorityString) -- +-- inv.priority.damTypeIsAllowed(damType, priorityName, level) -- inv.priority.locIsAllowed(wearableLoc, priorityName, level) -- -- inv.priority.addDefault() -- add some default priorities @@ -10896,7 +11022,7 @@ function inv.priority.create(priorityName, endTag) end -- inv.priority.create -function inv.priority.clone(origPriorityName, clonedPriorityName, endTag) +function inv.priority.clone(origPriorityName, clonedPriorityName, useVerbose, endTag) local retval if (clonedPriorityName == nil) or (clonedPriorityName == "") then @@ -10922,8 +11048,10 @@ function inv.priority.clone(origPriorityName, clonedPriorityName, endTag) -- Copy the priority into a new table entry inv.priority.table[clonedPriorityName] = dbot.table.getCopy(inv.priority.table[origPriorityName]) - dbot.info("Cloned priority \"@C" .. clonedPriorityName .. "@W\" from priority \"@C" .. - origPriorityName .."@W\"") + if useVerbose then + dbot.info("Cloned priority \"@C" .. clonedPriorityName .. "@W\" from priority \"@C" .. + origPriorityName .."@W\"") + end -- if -- Save the table with the new priority. We're done! :) retval = inv.priority.save() @@ -11592,6 +11720,44 @@ function inv.priority.stringToTable(priorityString) end -- inv.priority.stringToTable +-- Priorities can have lines of the form "~pierce 1 0 0 1 1" to indicate that +-- weapons with the "pierce" damtype should be ignored when creating the priority +-- equipment sets +function inv.priority.damTypeIsAllowed(damType, priorityName, level) + if (damType == nil) or (damType == "") then + dbot.warn("inv.priority.damTypeIsAllowed: Missing damType parameter") + return false + end -- if + + if (priorityName == nil) or (priorityName == "") then + dbot.warn("inv.priority.damTypeIsAllowed: Missing priority name parameter") + return false + end -- if + + level = tonumber(level or "") + if (level == nil) or (level < 1) or (level > 291) then + dbot.warn("inv.priority.damTypeIsAllowed: Invalid level parameter") + return false + end -- if + + -- Check if the specified priority exists for the specified level + local priorityTable, retval = inv.priority.get(priorityName, level) + if (priorityTable == nil) then + dbot.warn("inv.priority.damTypeIsAllowed: Priority \"" .. priorityName .. + "\" does not have a priority table " .. "for level " .. level) + return false + end -- if + + local value = tonumber(priorityTable["~" .. (string.lower(damType) or "")] or "") or 0 + if (value == 0) then + return true + else + return false + end -- if + +end -- inv.priority.damTypeIsAllowed + + -- Priorities can have lines of the form "~hold 1 0 0 1 1" to indicate that the -- "hold" location should be ignored when creating the priority equipment sets function inv.priority.locIsAllowed(wearableLoc, priorityName, level) @@ -12294,41 +12460,41 @@ function inv.priority.addDefault() ------------------------ -- This prioritizes defensive aspects of an equipment set local psiDefensePriority = { - str = 0.6, - int = 1.0, - wis = 1.0, - dex = 0.8, - con = 0.8, - luck = 1.0, - dam = 0.5, - hit = 0.5, - - avedam = 1.0, - offhandDam = 0.0, - - hp = 0.02, - mana = 0.01, - - sanctuary = 10, - haste = 0, - flying = 0, - invis = 1, - regeneration = 5, - detectinvis = 0, - detecthidden = 0, - detectevil = 0, - detectgood = 0, - dualwield = 0, - irongrip = 50, - shield = 50, - - maxint = 40, - maxwis = 40, - maxluck = 20, - - allmagic = 0.05, - allphys = 0.10 - } + str = 0.6, + int = 1.0, + wis = 1.0, + dex = 0.8, + con = 0.8, + luck = 1.0, + dam = 0.5, + hit = 0.5, + + avedam = 1.0, + offhandDam = 0.0, + + hp = 0.02, + mana = 0.01, + + sanctuary = 10, + haste = 0, + flying = 0, + invis = 1, + regeneration = 5, + detectinvis = 0, + detecthidden = 0, + detectevil = 0, + detectgood = 0, + dualwield = 0, + irongrip = 50, + shield = 50, + + maxint = 40, + maxwis = 40, + maxluck = 20, + + allmagic = 0.05, + allphys = 0.10 + } psiDefensePriority["~second"] = 1 -- Minor hack since the "~" messes up table keys retval = inv.priority.add( "psi-defense", @@ -12459,7 +12625,8 @@ inv.priority.fieldTable = { { "sonic" , "Value of 1 point of sonic magical resistance" }, { "water" , "Value of 1 point of water magical resistance" }, - { "~light" , "Set to 1 to disable the light location" }, +-- Note: We use "~light" to ignore the light damType not to ignore the light wearable location +-- { "~light" , "Set to 1 to disable the light location" }, { "~head" , "Set to 1 to disable the head location" }, { "~eyes" , "Set to 1 to disable the eyes location" }, { "~lear" , "Set to 1 to disable the left ear location" }, @@ -12489,7 +12656,30 @@ inv.priority.fieldTable = { { "~float" , "Set to 1 to disable the float location" }, { "~above" , "Set to 1 to disable the above location" }, { "~portal" , "Set to 1 to disable the portal location" }, - { "~sleeping" , "Set to 1 to disable the sleeping location" } + { "~sleeping" , "Set to 1 to disable the sleeping location" }, + + { "~bash" , "Set to 1 to disable weapons with damtype bash" }, + { "~pierce" , "Set to 1 to disable weapons with damtype pierce" }, + { "~slash" , "Set to 1 to disable weapons with damtype slash" }, + + { "~acid" , "Set to 1 to disable weapons with damtype acid" }, + { "~air" , "Set to 1 to disable weapons with damtype air" }, + { "~cold" , "Set to 1 to disable weapons with damtype cold" }, + { "~disease" , "Set to 1 to disable weapons with damtype disease" }, + { "~earth" , "Set to 1 to disable weapons with damtype earth" }, + { "~electric" , "Set to 1 to disable weapons with damtype electric" }, + { "~energy" , "Set to 1 to disable weapons with damtype energy" }, + { "~fire" , "Set to 1 to disable weapons with damtype fire" }, + { "~holy" , "Set to 1 to disable weapons with damtype holy" }, + { "~light" , "Set to 1 to disable weapons with damtype light" }, + { "~magic" , "Set to 1 to disable weapons with damtype magic" }, + { "~mental" , "Set to 1 to disable weapons with damtype mental" }, + { "~negative" , "Set to 1 to disable weapons with damtype negative" }, + { "~poison" , "Set to 1 to disable weapons with damtype poison" }, + { "~shadow" , "Set to 1 to disable weapons with damtype shadow" }, + { "~sonic" , "Set to 1 to disable weapons with damtype sonic" }, + { "~water" , "Set to 1 to disable weapons with damtype water" } + } @@ -13117,6 +13307,7 @@ function inv.set.createWithHandicap(priorityName, level, handicap) local objLevel = tonumber(inv.items.getStatField(objId, invStatFieldLevel) or "") local objWearable = inv.items.getStatField(objId, invStatFieldWearable) or "" local objWeight = tonumber(inv.items.getStatField(objId, invStatFieldWeight) or 0) + local objDamType = inv.items.getStatField(objId, invStatFieldDamType) or "" -- Strip out commas in the flags to make searching easier local objFlags = inv.items.getStatField(objId, invStatFieldFlags) or "" @@ -13139,6 +13330,11 @@ function inv.set.createWithHandicap(priorityName, level, handicap) dbot.debug("Skipping item: align=" .. (dbot.gmcp.getAlign() or "nil") .. ", flags=\"" .. (objFlags or "nil") .. "\"") + -- Check if the item is a weapon with a disallowed damage type + elseif (objDamType ~= nil) and (objDamType ~= "") and + (not inv.priority.damTypeIsAllowed(objDamType, priorityName, level)) then + -- Skip the current object because it is a weapon with a damtype we don't want + -- The alignment is acceptable. Check the other requirements... elseif (objWearable ~= nil) and (objWearable ~= "") and (inv.wearables[objWearable] ~= nil) then score, offhandScore = inv.score.item(objId, priorityName, handicap, level) @@ -14272,6 +14468,114 @@ function inv.set.covetCR() end -- inv.set.covetCR +---------------------------------------------------------------------------------------------------- +-- +-- Module to manage weapon-only equipment sets +-- +-- dinv weapon [next | ] +-- +-- inv.weapon.use(priorityName, damTypes, endTag) +-- inv.weapon.next(endTag) +-- +---------------------------------------------------------------------------------------------------- + +inv.weapon = {} +inv.weapon.priorityName = "weaponSet" + +function inv.weapon.use(priorityName, damTypes, endTag) + local retval = DRL_RET_SUCCESS + local weaponPriority = {} + + if (priorityName == nil) or (priorityName == "") then + dbot.warn("inv.weapon.use: Missing priority name") + return inv.tags.stop(invTagsSet, endTag, DRL_RET_INVALID_PARAM) + end -- if + + if (damTypes == nil) or (damTypes == "") then + dbot.warn("inv.weapon.use: Missing list of requested damage types") + return inv.tags.stop(invTagsSet, endTag, DRL_RET_INVALID_PARAM) + end -- if + + -- Remove any previous (and stale) weapon priority + if (inv.priority.table[inv.weapon.priorityName] ~= nil) then + retval = inv.priority.remove(inv.weapon.priorityName) + if (retval ~= DRL_RET_SUCCESS) then + dbot.warn("inv.weapon.use: Failed to remove weapon priority: " .. dbot.retval.getString(retval)) + return inv.tags.stop(invTagsSet, endTag, retval) + end -- if + end -- if + + -- Clone the specified priority so that we can tweak the clone and add damage type preferences + retval = inv.priority.clone(priorityName, inv.weapon.priorityName, false, nil) + if (retval ~= DRL_RET_SUCCESS) then + dbot.warn("inv.weapon.use: Failed to clone priority \"" .. priorityName .. "\": " .. + dbot.retval.getString(retval)) + return inv.tags.stop(invTagsSet, endTag, retval) + end -- if + + local damTypesToUse = string.lower(damTypes) + local allDamTypes = dbot.arrayConcat(dbot.physicalTypes, dbot.magicalTypes) + + -- For each priority block in the priority, specify if each possible damage type is allowed + for _, priBlock in ipairs(inv.priority.table[inv.weapon.priorityName] or {}) do + for _, damType in ipairs(allDamTypes) do + if dbot.isWordInString(damType, damTypesToUse) or + dbot.isWordInString("all", damTypesToUse) or + (dbot.isWordInString("phys", damTypesToUse) and dbot.isPhysical(damType)) or + (dbot.isWordInString("magic", damTypesToUse) and dbot.isMagical(damType)) or + (dbot.isWordInString("physical", damTypesToUse) and dbot.isPhysical(damType)) or + (dbot.isWordInString("magical", damTypesToUse) and dbot.isMagical(damType)) then + priBlock.priorities["~" .. damType] = 0 + else + priBlock.priorities["~" .. damType] = 1 + end -- if + end -- for + end -- for + + -- Wear the set that matches the weapon priority + return inv.set.createAndWear(inv.weapon.priorityName, dbot.gmcp.getLevel(), + inv.set.createIntensity, endTag) +end -- inv.weapon.use + + +function inv.weapon.next(endTag) + local retval + local level = dbot.gmcp.getLevel() + + -- Check if the weapon priority exists and has an associated weapon set + if (inv.priority.table[inv.weapon.priorityName] == nil) or + (inv.set.table[inv.weapon.priorityName] == nil) or + (inv.set.table[inv.weapon.priorityName][level] == nil) then + dbot.info("Skipped weapon request: Use \"@Gdinv weapon @W\" to specify types") + return DRL_RET_UNINITIALIZED + end -- if + + local wielded = inv.set.table[inv.weapon.priorityName][level].wielded + local second = inv.set.table[inv.weapon.priorityName][level].second + local currentDamType = "" + + if (wielded ~= nil) then + currentDamType = inv.items.getStatField(wielded.id, invStatFieldDamType) + elseif (second ~= nil) then + currentDamType = inv.items.getStatField(second.id, invStatFieldDamType) + else + dbot.info("Skipping next weapon request: No allowable weapon sets remain") + return inv.tags.stop(invTagsSet, endTag, DRL_RET_MISSING_ENTRY) + end -- if + + dbot.debug("inv.weapon.next: Current dam type is: \"" .. currentDamType .. "\"") + + -- Remove the current primary dam type for each priority block in the weapon set priority + for _, priBlock in ipairs(inv.priority.table[inv.weapon.priorityName] or {}) do + priBlock.priorities["~" .. string.lower(currentDamType)] = 1 + end -- for + + -- Wear the set that matches the updated weapon priority + return inv.set.createAndWear(inv.weapon.priorityName, level, inv.set.createIntensity, endTag) + +end -- inv.weapon.next + + ---------------------------------------------------------------------------------------------------- -- -- Module to manage snapshots of equipment sets @@ -17338,6 +17642,61 @@ function dbot.mergeFields(field1, field2) end -- dbot.mergeFields +---------------------------------------------------------------------------------------------------- +-- dbot.arrayConcat: Returns an array generated by concatenating the two input arrays +-- +-- For example: concatenating { "a", "b", "c" } and { "d", "e" } yields { "a", "b", "c", "d", "e" } +---------------------------------------------------------------------------------------------------- +function dbot.arrayConcat(array1, array2) + local mergedArray = {} + + for _, entry in ipairs(array1) do + table.insert(mergedArray, entry) + end -- for + + for _, entry in ipairs(array2) do + table.insert(mergedArray, entry) + end -- for + + return mergedArray +end -- dbot.arrayConcat + + +---------------------------------------------------------------------------------------------------- +-- dbot.isPhysical and dbot.isMagical return booleans indicating if the input parameter string is +-- one of the known physical or magical damage types +---------------------------------------------------------------------------------------------------- +--FIXME: use these throughout the plugin --v +dbot.physicalTypes = { invStatFieldBash, invStatFieldPierce, invStatFieldSlash } +dbot.magicalTypes = { invStatFieldAcid, invStatFieldCold, invStatFieldEnergy, + invStatFieldHoly, invStatFieldElectric, invStatFieldNegative, + invStatFieldShadow, invStatFieldMagic, invStatFieldAir, + invStatFieldEarth, invStatFieldFire, invStatFieldLight, + invStatFieldMental, invStatFieldSonic, invStatFieldWater, + invStatFieldDisease, invStatFieldPoison } + +function dbot.isPhysical(damType) + for _, physType in ipairs(dbot.physicalTypes) do + if (physType == damType) then + return true + end -- if + end -- for + + return false +end -- dbot.isPhysical + + +function dbot.isMagical(damType) + for _, magType in ipairs(dbot.magicalTypes) do + if (magType == damType) then + return true + end -- if + end -- for + + return false +end -- dbot.isMagical + + ---------------------------------------------------------------------------------------------------- -- dbot.deleteTrigger: Wrapper around DeleteTrigger that checks the mush error codes ----------------------------------------------------------------------------------------------------