Last active
September 22, 2015 14:43
-
-
Save IndigoFenix/c487191fa3a857820b0d to your computer and use it in GitHub Desktop.
Enables soul manipulation and constructing creatures in reactions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
creature_sscc_examples | |
[OBJECT:CREATURE] | |
Special classes for use with the SoulShuffle and ConstructCreature scripts: | |
DFHACK_CONSTRUCTCREATURE_CASTECOLORS - Uses the caste color system to make creatures show up with the color of their construction material. See below. | |
DFHACK_CONSTRUCTCREATURE_ITEMCORPSE - Causes the creature's itemcorpse to take on the same material as its first defined material. | |
DFHACK_SOULSHUFFLE_SCUTTLE_ON_REMOVAL - The creature will die when its soul is removed. | |
DFHACK_SOULSHUFFLE_VANISH_ON_REMOVAL - The creature will vanish into smoke when its soul is removed. | |
DFHACK_SOULSHUFFLE_CANNOT_TARGET - The creature cannot be targeted by SoulShuffle reactions. | |
DFHACK_SOULSHUFFLE_NO_SOUL_NEEDED - The creature will not go catatonic when its soul is removed. (It will still not be able to learn.) | |
DFHACK_SOULSHUFFLE_RETAIN_LOYALTY - The creature will retain its original loyalty even if its soul is replaced or removed. Use this for 'soul container' creatures. | |
DFHACK_SOULSHUFFLE_ONE_SOUL_LIMIT - The creature cannot store more than one soul at a time. | |
[CREATURE:IMP_SOUL] | |
[CREATURE_CLASS:DFHACK_SOULSHUFFLE_VANISH_ON_REMOVAL] -- Causes the creature to vanish like a bogeyman when the last soul is removed | |
[CREATURE_CLASS:DFHACK_SOULSHUFFLE_ONE_SOUL_LIMIT] -- Prevents you from adding extra souls to the creature. | |
[DESCRIPTION:A soul manifested as a small, flying creature.] | |
[NAME:soul imp:soul imps:soul imp] | |
[CASTE_NAME:soul imp:soul imps:soul imp] | |
[CREATURE_TILE:'i'][COLOR:6:0:1] | |
[CANOPENDOORS] | |
[PREFSTRING:strange features] | |
[BODY:HUMANOID_NECK:TAIL:2EYES:2EARS:NOSE:HEART:GUTS:ORGANS:HUMANOID_JOINTS:THROAT:NECK:SPINE:BRAIN:SKULL:MOUTH:TONGUE:4FINGERS:4TOES:FACIAL_FEATURES:GENERIC_TEETH_WITH_LARGE_EYE_TEETH:RIBCAGE] | |
[BODY_DETAIL_PLAN:STANDARD_MATERIALS] | |
[BODY_DETAIL_PLAN:STANDARD_TISSUES] | |
[BODY_DETAIL_PLAN:VERTEBRATE_TISSUE_LAYERS:SKIN:FAT:MUSCLE:BONE:CARTILAGE] | |
[USE_MATERIAL_TEMPLATE:NAIL:NAIL_TEMPLATE] | |
[USE_TISSUE_TEMPLATE:NAIL:NAIL_TEMPLATE] | |
[TISSUE_LAYER:BY_CATEGORY:FINGER:NAIL:FRONT] | |
[TISSUE_LAYER:BY_CATEGORY:TOE:NAIL:FRONT] | |
[SELECT_TISSUE_LAYER:HEART:BY_CATEGORY:HEART] | |
[PLUS_TISSUE_LAYER:SKIN:BY_CATEGORY:THROAT] | |
[TL_MAJOR_ARTERIES] | |
[BODY_DETAIL_PLAN:STANDARD_HEAD_POSITIONS] | |
[BODY_DETAIL_PLAN:HUMANOID_HEAD_POSITIONS] | |
[BODY_DETAIL_PLAN:HUMANOID_RIBCAGE_POSITIONS] | |
[BODY_DETAIL_PLAN:HUMANOID_RELSIZES] | |
[USE_MATERIAL_TEMPLATE:SINEW:SINEW_TEMPLATE] | |
[TENDONS:LOCAL_CREATURE_MAT:SINEW:200] | |
[LIGAMENTS:LOCAL_CREATURE_MAT:SINEW:200] | |
[HAS_NERVES] | |
[NO_DRINK][NO_EAT][NO_SLEEP] | |
[USE_MATERIAL_TEMPLATE:GOO:GOO_TEMPLATE] | |
[BLOOD:LOCAL_CREATURE_MAT:GOO:LIQUID] | |
[BODY_SIZE:0:0:6000] | |
[BODY_APPEARANCE_MODIFIER:HEIGHT:90:95:98:100:102:105:110] | |
[BODY_APPEARANCE_MODIFIER:BROADNESS:90:95:98:100:102:105:110] | |
[ATTACK:BITE:CHILD_BODYPART_GROUP:BY_CATEGORY:HEAD:BY_CATEGORY:TOOTH] | |
[ATTACK_SKILL:BITE] | |
[ATTACK_VERB:bite:bites] | |
[ATTACK_CONTACT_PERC:100] | |
[ATTACK_PENETRATION_PERC:100] | |
[ATTACK_FLAG_EDGE] | |
[ATTACK_PREPARE_AND_RECOVER:3:3] | |
[ATTACK_PRIORITY:MAIN] | |
[ATTACK_FLAG_CANLATCH] | |
[ATTACK:PUNCH:BODYPART:BY_TYPE:GRASP] | |
[ATTACK_SKILL:GRASP_STRIKE] | |
[ATTACK_VERB:punch:punches] | |
[ATTACK_CONTACT_PERC:100] | |
[ATTACK_PREPARE_AND_RECOVER:3:3] | |
[ATTACK_FLAG_WITH] | |
[ATTACK_PRIORITY:MAIN] | |
[ATTACK:KICK:BODYPART:BY_TYPE:STANCE] | |
[ATTACK_SKILL:STANCE_STRIKE] | |
[ATTACK_VERB:kick:kicks] | |
[ATTACK_CONTACT_PERC:100] | |
[ATTACK_PREPARE_AND_RECOVER:4:4] | |
[ATTACK_FLAG_WITH] | |
[ATTACK_PRIORITY:SECOND] | |
[ATTACK_FLAG_BAD_MULTIATTACK] | |
[ATTACK:SCRATCH:CHILD_TISSUE_LAYER_GROUP:BY_TYPE:GRASP:BY_CATEGORY:FINGER:NAIL] | |
[ATTACK_SKILL:GRASP_STRIKE] | |
[ATTACK_VERB:scratch:scratches] | |
[ATTACK_CONTACT_PERC:100] | |
[ATTACK_PENETRATION_PERC:100] | |
[ATTACK_FLAG_EDGE] | |
[ATTACK_PREPARE_AND_RECOVER:3:3] | |
[ATTACK_PRIORITY:SECOND] | |
[EQUIPS] | |
[ALL_ACTIVE] | |
[NO_FEVERS] | |
[HOMEOTHERM:10095] | |
[APPLY_CREATURE_VARIATION:STANDARD_BIPED_GAITS:900:730:561:351:1900:2900] 25 kph | |
[APPLY_CREATURE_VARIATION:STANDARD_CLIMBING_GAITS:2990:2257:1525:731:4300:6100] 12 kph | |
[APPLY_CREATURE_VARIATION:STANDARD_SWIMMING_GAITS:2990:2257:1525:731:4300:6100] 12 kph | |
[APPLY_CREATURE_VARIATION:STANDARD_CRAWLING_GAITS:2990:2257:1525:731:4300:6100] 12 kph | |
[NATURAL_SKILL:CLIMBING:15] | |
[SWIMS_INNATE] | |
[SET_TL_GROUP:BY_CATEGORY:ALL:SKIN] | |
[TL_COLOR_MODIFIER:RED:1] | |
[TLCM_NOUN:skin:SINGULAR] | |
[SET_TL_GROUP:BY_CATEGORY:EYE:EYE] | |
[TL_COLOR_MODIFIER:BLACK:1] | |
[TLCM_NOUN:eyes:PLURAL] | |
[CASTE:DEFAULT_FEMALE] | |
[FEMALE] | |
[CASTE:DEFAULT_MALE] | |
[MALE] | |
[CASTE:DEFAULT] | |
[SELECT_CASTE:ALL] | |
[SELECT_MATERIAL:GOO] | |
[MELTING_POINT:10000] | |
[CREATURE:GEM_SOUL] | |
[CREATURE_CLASS:DFHACK_CONSTRUCTCREATURE_ITEMCORPSE] -- Replaces the itemcorpse's material with the FIRST DEFINED MATERIAL in the creature. To simplify this, define both the main material and the tissue first. | |
[CREATURE_CLASS:DFHACK_CONSTRUCTCREATURE_CASTECOLORS] -- Uses the color of the FIRST DEFINED MATERIAL to determine the creature's caste. The castes must be defined properly, as they are here. | |
[CREATURE_CLASS:DFHACK_SOULSHUFFLE_SCUTTLE_ON_REMOVAL] -- Scuttles the creature when its last soul is removed. | |
[CREATURE_CLASS:DFHACK_SOULSHUFFLE_ONE_SOUL_LIMIT] -- Prevents you from adding extra souls to the creature. | |
[CREATURE_CLASS:DFHACK_SOULSHUFFLE_RETAIN_LOYALTY] -- Normally, a creature will take on the loyalty of its current soul. This tag prevents this, making it stay loyal to your civ. | |
[NAME:soul gem:soul gems:soul gem] | |
[CASTE_NAME:soul gem:soul gems:soul gem] | |
[DESCRIPTION:A tiny gem that contains a soul.] | |
[CREATURE_TILE:4][COLOR:0:0:1] | |
[PET] | |
[USE_MATERIAL_TEMPLATE:GEM_MATERIAL:STONE_TEMPLATE] -- This is the material that will be replaced by the GEM_MATERIAL in the reaction. | |
[TISSUE:GEM_TISSUE] | |
[TISSUE_NAME:gemstone:NP] | |
[TISSUE_MATERIAL:LOCAL_CREATURE_MAT:GEM_MATERIAL] | |
[RELATIVE_THICKNESS:1] | |
[STRUCTURAL][FUNCTIONAL][CONNECTS] | |
[TISSUE_SHAPE:LAYER] | |
[BODY:BASIC_1PARTBODY_THOUGHT] | |
[TISSUE_LAYER:BY_TYPE:UPPERBODY:GEM_TISSUE] | |
[IMMOBILE][NOPAIN][NONAUSEA][NO_EAT][NO_DRINK][NOBREATHE][NO_FEVERS][PARALYZEIMMUNE][FIREIMMUNE][NOEXERT][NOT_LIVING] | |
[BODY_SIZE:0:0:5] | |
[CASTE:00][CASTE_COLOR:0:0:0] | |
[CASTE:10][CASTE_COLOR:1:0:0] | |
[CASTE:20][CASTE_COLOR:2:0:0] | |
[CASTE:30][CASTE_COLOR:3:0:0] | |
[CASTE:40][CASTE_COLOR:4:0:0] | |
[CASTE:50][CASTE_COLOR:5:0:0] | |
[CASTE:60][CASTE_COLOR:6:0:0] | |
[CASTE:70][CASTE_COLOR:7:0:0] | |
[CASTE:01][CASTE_COLOR:0:0:1] | |
[CASTE:11][CASTE_COLOR:1:0:1] | |
[CASTE:21][CASTE_COLOR:2:0:1] | |
[CASTE:31][CASTE_COLOR:3:0:1] | |
[CASTE:41][CASTE_COLOR:4:0:1] | |
[CASTE:51][CASTE_COLOR:5:0:1] | |
[CASTE:61][CASTE_COLOR:6:0:1] | |
[CASTE:71][CASTE_COLOR:7:0:1] | |
[SELECT_CASTE:ALL] | |
[ITEMCORPSE:SMALLGEM:NONE:LOCAL_CREATURE_MAT:GEM_MATERIAL] -- This material will be replaced by the material used to construct the creature. NOTE: The material must still be the first one defined. | |
--A construct to demonstrate more complex creature building. This creature has three materials (SHELL, CLOCKWORK, and BRAIN), all of which can be defined in the reaction. | |
[CREATURE:BOT_SOUL] | |
[CREATURE_CLASS:DFHACK_CONSTRUCTCREATURE_CASTECOLORS] -- Same as above. Here, the material used for the caste will be the SHELL. | |
[CREATURE_CLASS:DFHACK_SOULSHUFFLE_NO_SOUL_NEEDED] -- Prevents the creature from going catatonic when its last soul is removed. Defining this for civ members will cause the unit viewing screen to not display properly. It's okay for pets though. | |
[NAME:homunculus:homunculi:homunculus] | |
[CASTE_NAME:homunculus:homunculi:homunculus] | |
[DESCRIPTION:An tiny artificial humanoid. Although it can function autonomously, it requires a soul in order to learn.] | |
[COLOR:0:0:1] | |
[PET] | |
[USE_MATERIAL_TEMPLATE:SHELL:METAL_TEMPLATE] | |
[USE_MATERIAL_TEMPLATE:CLOCKWORK:STONE_TEMPLATE] | |
[USE_MATERIAL_TEMPLATE:BRAIN:STONE_TEMPLATE] | |
[USE_MATERIAL_TEMPLATE:BLOOD:BLOOD_TEMPLATE] | |
[TISSUE:SHELL] | |
[TISSUE_NAME:plating:NP] | |
[TISSUE_MATERIAL:LOCAL_CREATURE_MAT:SHELL] | |
[RELATIVE_THICKNESS:2] | |
[CONNECTS][FUNCTIONAL] | |
[TISSUE_SHAPE:LAYER] | |
[TISSUE:CLOCKWORK] | |
[TISSUE_NAME:clockwork:NP] | |
[TISSUE_MATERIAL:LOCAL_CREATURE_MAT:CLOCKWORK] | |
[RELATIVE_THICKNESS:1] | |
[FUNCTIONAL] | |
[VASCULAR:5] | |
[CONNECTS] | |
[TISSUE_SHAPE:LAYER] | |
[TISSUE:HYDRAULICS] | |
[TISSUE_NAME:hydraulics:NP] | |
[TISSUE_MATERIAL:LOCAL_CREATURE_MAT:CLOCKWORK] --Materials are replaced, not tissues. This will have the same new material as the above tissue. | |
[RELATIVE_THICKNESS:1] | |
[MUSCULAR][STRUCTURAL] | |
[FUNCTIONAL] | |
[ARTERIES] | |
[VASCULAR:5] | |
[CONNECTS] | |
[TISSUE_SHAPE:LAYER] | |
[USE_TISSUE_TEMPLATE:BRAIN:BRAIN_TEMPLATE] | |
[HAS_NERVES] | |
[BLOOD:LOCAL_CREATURE_MAT:BLOOD:LIQUID] | |
[CAN_LEARN][CAN_SPEAK] | |
[EXTRAVISION][NOPAIN][NONAUSEA][NO_EAT][NO_DRINK][NOBREATHE][NO_FEVERS][PARALYZEIMMUNE][FIREIMMUNE][NOEXERT][NOT_LIVING] | |
[BODY:HUMANOID_SIMPLE:BRAIN] | |
[BODY_DETAIL_PLAN:EXOSKELETON_TISSUE_LAYERS:SHELL:CLOCKWORK:HYDRAULICS] | |
[APPLY_CREATURE_VARIATION:PUNCH_ATTACK] | |
[APPLY_CREATURE_VARIATION:KICK_ATTACK] | |
[BODY_SIZE:0:0:4000] | |
[CASTE:00][CASTE_COLOR:0:0:0] | |
[CASTE:10][CASTE_COLOR:1:0:0] | |
[CASTE:20][CASTE_COLOR:2:0:0] | |
[CASTE:30][CASTE_COLOR:3:0:0] | |
[CASTE:40][CASTE_COLOR:4:0:0] | |
[CASTE:50][CASTE_COLOR:5:0:0] | |
[CASTE:60][CASTE_COLOR:6:0:0] | |
[CASTE:70][CASTE_COLOR:7:0:0] | |
[CASTE:01][CASTE_COLOR:0:0:1] | |
[CASTE:11][CASTE_COLOR:1:0:1] | |
[CASTE:21][CASTE_COLOR:2:0:1] | |
[CASTE:31][CASTE_COLOR:3:0:1] | |
[CASTE:41][CASTE_COLOR:4:0:1] | |
[CASTE:51][CASTE_COLOR:5:0:1] | |
[CASTE:61][CASTE_COLOR:6:0:1] | |
[CASTE:71][CASTE_COLOR:7:0:1] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
reaction_sscc_examples | |
[OBJECT:REACTION] | |
The Soulshuffle script uses reaction codes as commands. | |
Explanation: | |
Every Soulshuffle reaction must begin with LUA_HOOK_SOULSHUFFLE, which is followed by a set of commands separated by spaces. | |
The first word is the operation name. Operations are MOVE, ASSIMILATE, SWAP, SPLIT, MERGE, COPY, FUSE, and DESTROY. | |
Each operation has two subjects: the source and the target. The source is specified with FROM, the target with TO. | |
Many source and target specifiers can be specified, but they are exclusive. In order to be considered a valid target, a unit must satisfy ALL of the specifier commands. | |
MOVE - moves a selected soul from the source unit to the target unit.) | |
REPLACE - moves the selected soul and destroys the target. | |
DESTROY - destroys the source soul. Does not require a target to be specified. | |
ASSIMILATE - causes the target soul's civ to match the source soul's civ. | |
SWAP - switches the selected source and target souls. | |
SPLIT - splits the source soul in half and moves it to the target unit. Each fragment will acquire a percentage (default 50) of the original's skills and mental attributes. | |
COPY - similar to SPLIT, but the source soul does not lose any skills or attributes. The percentage determines the amount of the original's skills and attributes copied. | |
MERGE - merges the source soul into the target soul. The skills and attributes will be averaged out based on a weighted average. | |
FUSE - similar to MERGE, but a percentage of the skills and attributes will be added from the source soul to the target soul (default 100), making the new soul stronger. | |
Source/target specifier strings: | |
Almost all basic creature tags can be targeted, as well as most behavioral flags. I won't list them all here, but if you can specify it in a creature, it can probably be used as a parameter, and many, many other possible things can also be targeted. | |
SELF - the unit running the reaction. | |
ALIVE - conventionally alive, i.e. not dead, ghostly, or undead. | |
CIV - is a member of the player's civilization. This does include tame animals. | |
CLASS - Allows targeting based on creature class. Syntax is (for example) CLASS GENERAL_POISON. | |
CREATURE - Allows targeting based on creature. Syntax is CREATURE RACE CASTE. | |
CAGED - is in a cage. | |
TAME - is a tame pet. | |
GHOSTLY - is a ghost. (I'm not sure what happens if you take the soul out of a ghost. Best not chance it for now.) | |
DEMON - is a demon. | |
UNDERWORLD - is a demon currently in 'attack mode'. | |
RESIDENT - most hostile local creatures have this flag: demons, underground civs, etc. | |
DEAD - is completely dead. Ghosts and undead are NOT considered DEAD. Note that this does not target dead bodies, it targets dead UNITS, which generally remain in the place they were when they died (or where their ghost was put to rest), even if the body moves around. | |
All of these specifiers can be inverted by preceding them with NOT. | |
Two special specifiers: SPAWN and CONSTRUCT: | |
SPAWN creates a new creature with the specified race and caste, and implants the soul in it. SPAWN RACE CASTE. If CASTE is NONE, the script will try and find the most logical target to turn the soul into, based on the caste's name. It does this by searching the caste name and looking for matches. For instance, if you make a race that (hypothetically speaking) turns human souls into devils and elvish souls into cambions, male devils can be given the caste name HUMAN MALE and female cambions can be given the caste name ELF FEMALE. And so on. If a match is not found, it will look for castes called DEFAULT_MALE and DEFAULT_FEMALE, and finally DEFAULT if neither of those match. If no matches are found, a random caste will be selected. | |
CONSTRUCT is similar, but utilizes the CONSTRUCTCREATURE system that will use the reagents to construct the creature. | |
SPAWN and CONSTRUCT only make sense for MOVE, SPLIT, and COPY operations. | |
SPLIT, COPY, MERGE, and FUSE can also be given a PERC value (on the source soul). This basically determines the percentage of the soul that will be affected by the reaction. | |
ATTACK is a modifier that can be added to any reaction. Syntax is ATTACK [combat intensity] [success rate]. Default for both is 100. | |
When a reaction uses the ATTACK modifier, the reaction can fail when attempting to use it on souls that are not part of your civ. | |
The difficulty in overcoming resistance is based on the combined soul attributes of the souls being affected, as well as their respective stress levels. | |
When engaging in 'soul combat', both souls will take 'damage', represented by their stress level. | |
CAN_BREAK is a modifier that can cause a soul to break when it is being manipulated. Weaker souls (with smaller attribute values) have a higher chance of breaking, as do highly stressed souls. Use this to limit how small you can divide up a soul. | |
MIN_ATTR and MAX_ATTR place limits on soul manipulation based on average soul attributes. Use this to limit splitting and fusing souls. | |
There are also some special specifier strings: | |
TOP: Only allows selection of the unit's current soul. When in the target unit in a MOVE or SPLIT operation, this will cause the implanted soul to be set as the target's current soul. | |
SUB: Opposite of TOP. Forbids selection of the current soul. | |
LIMIT: Prevents you from selecting a source unit if it has only one soul left. | |
EMPTY: Only allows selection of targets that have no soul. | |
SAME: Can be specified for the target to force the use of the source unit. | |
RANGE: Allows you to change the range that targets can be acquired at. Syntax is (for example) RANGE 50. Range is a square around the target unit. | |
Z: Like RANGE, but determines Z-levels instead. | |
A creature can contain multiple souls, however only one will be manifest at a time. Souls contain a unit's skills, personality, stress level, and thoughts. | |
Souls have a civ identity. Beware of manifesting the souls of hostile units without assimilating them first! | |
A creature can live without a soul. Extracting the last soul from a creature using Soulshuffle will cause it to go catatonic. Replacing it with a new soul will return the creature to normal. If you remove the current soul of a creature but it still has extra souls remaining, one will be selected at random. | |
Examples: | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE FROM ALIVE TO ALIVE] | |
[NAME:Move soul of one creature to another] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE SPLIT FROM ALIVE MIN_ATTR 250 TO ALIVE] | |
[NAME:Split a soul] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE COPY FROM ALIVE TO ALIVE] | |
[NAME:Copy a soul] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MERGE FROM ALIVE TO ALIVE] | |
[NAME:Merge two souls] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE FUSE FROM ALIVE MAX_ATTR 2500 TO ALIVE MAX_ATTR 2500] | |
[NAME:Fuse two souls] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE ATTACK FROM CREATURE GEM_SOUL NONE CIV_SOUL TO ALIVE SUB RANGE 200 Z 10] | |
[NAME:Cast a sleeper soul from gem] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE ATTACK FROM ALIVE SUB CIV_SOUL RANGE 1000 Z 1000 TO CONSTRUCT GEM_SOUL NONE PET] | |
[NAME:Recall sleeper soul to gem] | |
[REAGENT:GEM_MATERIAL:1:SMALLGEM:NONE:NONE:NONE] -- The name of the reagent must match the name of the material specified in the creature. | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE ATTACK FROM ALIVE NOT SELF SUB CIV_SOUL RANGE 1000 Z 1000 TO SAME TOP] | |
[NAME:Sleeper soul possession] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE FROM SELF LIMIT TO SAME TOP] | |
[NAME:Manifest a different soul] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE ATTACK FROM SELF TOP TO ALIVE NOT SELF TOP RANGE 1000 Z 1000] | |
[NAME:Attempt to possess a unit] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE FROM CREATURE GEM_SOUL NONE TO ALIVE NOT SELF EMPTY] | |
[NAME:Transfer soul from gem into empty shell] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE ASSIMILATE ATTACK FROM SELF TOP TO CREATURE GEM_SOUL NONE NOT CIV_SOUL] | |
[NAME:Assimilate stored soul into culture] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE SWAP ATTACK FROM ALIVE CAGED TOP TO ALIVE CAGED TOP] | |
[NAME:Swap souls of caged creatures] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE SWAP ATTACK FROM SELF TOP TO ALIVE NOT SELF TOP] | |
[NAME:Swap souls with nearby unit] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MERGE ATTACK FROM SELF LIMIT TO SELF] | |
[NAME:Mix souls inside body] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE ATTACK 100 200 MOVE FROM DEAD TO CONSTRUCT GEM_SOUL NONE PET RANGE 1000 Z 1000] | |
[NAME:Capture lost spirits in gem] | |
[REAGENT:GEM_MATERIAL:1:SMALLGEM:NONE:NONE:NONE] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE DESTROY FROM CREATURE GEM_SOUL NONE] | |
[NAME:Burn soul from gem] | |
[BUILDING:SOULPOWER:NONE] | |
[PRODUCT:100:1:BAR:NO_SUBTYPE:COAL:COKE][PRODUCT_DIMENSION:150] | |
[REACTION:LUA_HOOK_SOULSHUFFLE FUSE FROM CREATURE GEM_SOUL NONE PERC 10 TO SELF TOP] | |
[NAME:Consume soul of stored creature] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE ATTACK FROM DEMON RANGE 1000 Z 1000 TO CONSTRUCT GEM_SOUL NONE PET] | |
[NAME:Capture demon soul and store it in a gem] | |
[REAGENT:GEM_MATERIAL:1:SMALLGEM:NONE:NONE:NONE] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE SPLIT FROM SELF TOP PERC 10 ADD_STRESS 1000 MIN_ATTR 250 TO SPAWN IMP_SOUL NONE PET] -- SPAWN creates a new creature to contain the soul. This creature will have the same loyalty as the original soul. | |
[NAME:Splinter soul to produce an imp] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE FUSE FROM CREATURE IMP_SOUL NONE MAX_ATTR 1000 TO SELF TOP MAX_ATTR 5000] | |
[NAME:Fuse with soul imp] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MOVE ATTACK FROM ALIVE TO CONSTRUCT GEM_SOUL NONE PET] -- CONSTRUCT works the same as SPAWN, except that it uses the reagent items to define the creature's materials. | |
[NAME:Store a soul in a gem] | |
[REAGENT:GEM_MATERIAL:1:SMALLGEM:NONE:NONE:NONE] -- The name of the reagent must match the name of the material specified in the creature. | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_SOULSHUFFLE MERGE FROM CREATURE GEM_SOUL NONE TO CREATURE GEM_SOUL NONE NOT SAME] | |
[NAME:Combine two souls stored in gems] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
[REACTION:LUA_HOOK_CONSTRUCTCREATURE BOT_SOUL NONE NOSOUL PET] -- Adding NOSOUL will cause the creature to be generated without a soul. | |
[NAME:Create a homunculus] | |
[REAGENT:SHELL:1:BLOCKS:NONE:NONE:NONE] | |
[REAGENT:CLOCKWORK:1:TRAPPARTS:NONE:NONE:NONE] | |
[REAGENT:BRAIN:1:SMALLGEM:NONE:NONE:NONE] | |
[REAGENT:BLOOD:150:DRINK:NONE:NONE:NONE] -- It's a dwarfy homunculus :) | |
[REAGENT:drink container:1:NONE:NONE:NONE:NONE] | |
[CONTAINS:BLOOD] | |
[PRODUCT:0:0:BOULDER:NONE:NONE:NONE] | |
[BUILDING:SOULPOWER:NONE] | |
--Please note: Constructing complex creatures can be finicky. | |
The script isn't that great at figuring out which job item is matched up to which reagent, especially where the reagent is unclear about which item it should be using. | |
NONE:NONE:NONE:NONE as a construction material, for example, may confuse the script, as will using two items of the same type. | |
Also, requiring more than one item in a reagent will usually not work! | |
(Note: I found a new method, the above may no longer apply. Further testing is needed to be sure.) | |
The best way of constructing complex creatures (that require more than one material) is to use tools as intermediary items. Create a 'robot body' tool using the Metalsmith's Forge or a custom reaction, then use the tool as a reagent to construct the creature. | |
You do not need to define every material the creature uses in the reaction. The script creates the creature using its regular materials first, and only afterwards replaces the relevant materials. | |
So if you leave out a material in the reaction, the creature will simply use its normal body material. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Allows many functions relating to the manipulation of bodies and souls. | |
--[[ | |
By IndigoFenix | |
]]-- | |
--[[ | |
DFHACK_CONSTRUCTCREATURE_CASTECOLORS | |
DFHACK_CONSTRUCTCREATURE_ITEMCORPSE | |
DFHACK_SOULSHUFFLE_SCUTTLE_ON_REMOVAL | |
DFHACK_SOULSHUFFLE_VANISH_ON_REMOVAL | |
DFHACK_SOULSHUFFLE_CANNOT_TARGET | |
DFHACK_SOULSHUFFLE_NO_SOUL_NEEDED | |
DFHACK_SOULSHUFFLE_RETAIN_LOYALTY | |
DFHACK_SOULSHUFFLE_ONE_SOUL_LIMIT | |
]]-- | |
local eventful = require 'plugins.eventful' | |
local mo = require 'makeown' | |
local utils = require 'utils' | |
local script = require 'gui.script' | |
local soulStorageUnitId = nil | |
local soulStorageUnit = nil | |
local prefix = 'SOULSHUFFLE' | |
local rando=dfhack.random.new() | |
function closeBy(unit,u,radius,z) | |
if z == nil then z = 0 end | |
if unit.id ~= u.id | |
and unit.pos.x+radius >= u.pos.x | |
and unit.pos.x-radius <= u.pos.x | |
and unit.pos.y+radius >= u.pos.y | |
and unit.pos.y-radius <= u.pos.y | |
and unit.pos.z+z >= u.pos.z | |
and unit.pos.z-z <= u.pos.z | |
then return true | |
else | |
return false | |
end | |
end | |
function hasClass(unit,class) | |
local creature_classes = df.global.world.raws.creatures.all[unit.race].caste[unit.caste].creature_class | |
for i=0,#creature_classes-1,1 do | |
if creature_classes[i].value == class then return true end | |
end | |
return false | |
end | |
function getSoulStorageUnitId() | |
if soulStorageUnitId ~= nil then | |
return soulStorageUnitId | |
else | |
local data = dfhack.persistent.get(prefix..'_STORAGE') | |
if data == nil then | |
local unit=df.unit:new() | |
unit.id=df.global.unit_next_id | |
soulStorageUnitId = unit.id | |
df.global.unit_next_id=df.global.unit_next_id+1 | |
df.global.world.units.all:insert("#",unit) | |
--df.global.world.units.active:insert("#",unit) | |
dfhack.persistent.save({key=prefix..'_STORAGE'}) | |
data = dfhack.persistent.get(prefix..'_STORAGE') | |
data.ints[1] = soulStorageUnitId | |
data:save() | |
print ('Saved abstract soul storage unit with id: '..soulStorageUnitId) | |
return soulStorageUnitId | |
else | |
soulStorageUnitId = data.ints[1] | |
return soulStorageUnitId | |
end | |
end | |
end | |
function getSoulStorageUnit() | |
return df.unit.find(getSoulStorageUnitId()) | |
end | |
function InsertSoul(unit,soul,top) | |
isEmptyShell = false | |
if #unit.status.souls == 0 then | |
isEmptyShell = true | |
end | |
unit.status.souls:insert("#",soul) | |
if isEmptyShell or top == true then | |
unit.status.current_soul = soul | |
if unit.mood == 9 then | |
unit.mood = -1 | |
unit.animal.vanish_countdown = 0 | |
end | |
end | |
setCurrentSoulValues(unit) | |
return soul | |
end | |
function RemoveSoul(unit,soul) | |
foundSoul = false | |
for i = #unit.status.souls - 1,0, -1 do | |
s = unit.status.souls[i] | |
if s.unit_id == soul.unit_id then | |
unit.status.souls:erase(i) | |
foundSoul = true | |
end | |
end | |
if unit.status.current_soul ~= nil and unit.status.current_soul.unit_id == soul.unit_id then | |
unit.status.current_soul = nil | |
foundSoul = true | |
end | |
--Organize souls after removal. If there is no current soul, a random soul is picked from the unit's existing souls. If there are none left, unit goes catatonic. | |
if #unit.status.souls == 0 then | |
unit.status.current_soul = nil | |
end | |
if unit.status.current_soul == nil then | |
if #unit.status.souls == 0 then | |
if not hasClass(unit,'DFHACK_SOULSHUFFLE_NO_SOUL_NEEDED') then unit.mood = 9 end | |
if hasClass(unit,'DFHACK_SOULSHUFFLE_VANISH_ON_REMOVAL') then | |
unit.animal.vanish_countdown = 2 | |
elseif hasClass(unit,'DFHACK_SOULSHUFFLE_SCUTTLE_ON_REMOVAL') then | |
unit.flags3.scuttle = true | |
end | |
else | |
unit.status.current_soul = unit.status.souls[math.random(#unit.status.souls)-1] | |
end | |
end | |
setCurrentSoulValues(unit) | |
if foundSoul then | |
return soul | |
else | |
return nil | |
end | |
end | |
function FindSoul(unit,soul_id) | |
soul = nil | |
for i = 0, #unit.status.souls - 1, 1 do | |
s = unit.status.souls[i] | |
if s.unit_id == soul_id then | |
soul = unit.status.souls[i] | |
foundSoul = true | |
end | |
end | |
if unit.status.current_soul.unit_id == soul_id then | |
foundSoul = unit.status.current_soul | |
end | |
return soul | |
end | |
function InsertSoulAbstract(soul) | |
return InsertSoul(getSoulStorageUnit(),soul) | |
end | |
function RemoveSoulAbstract(soul) | |
return RemoveSoul(getSoulStorageUnit(),soul) | |
end | |
function FindSoulAbstract(soul_id) | |
return FindSoul(getSoulStorageUnit(),soul_id) | |
end | |
function exile(u) | |
if u then | |
u.civ_id=-1 | |
u.flags1.tame=false | |
u.flags1.forest=true | |
u.relations.following = nil | |
u.flags1.active_invader = false | |
u.flags1.hidden_in_ambush = false | |
u.flags1.coward = false | |
u.flags1.hidden_ambusher = false | |
u.flags1.invades = false | |
end | |
end | |
function setCurrentSoulValues(unit,override) | |
if hasClass(unit,'DFHACK_SOULSHUFFLE_RETAIN_LOYALTY') then return end | |
if unit.status.current_soul ~= nil then | |
local soul = unit.status.current_soul | |
unit.civ_id = soul.personality.civ_id | |
unit.cultural_identity = soul.personality.cultural_identity | |
if unit.civ_id == -1 then | |
soulrace = df.global.world.raws.creatures.all[soul.race] | |
casteflags = df.global.world.raws.creatures.all[soul.race].caste[soul.caste].flags | |
if casteflags.DEMON == true or casteflags.TITAN == true or casteflags.FEATURE_BEAST == true then | |
unit.flags1.tame = false | |
else | |
exile(unit) | |
end | |
end | |
else | |
unit.civ_id = -1 | |
unit.cultural_identity = -1 | |
end | |
end | |
function saveJobItemMats(reaction,jobitems) | |
items = {} | |
for i,v in ipairs(jobitems or {}) do | |
items[reaction.reagents[v.job_item_idx].code] = dfhack.matinfo.decode(v.item) | |
end | |
return items | |
end | |
--Does this actually work? Not sure... | |
function getReagentItemByCode(reaction,jobmats,code) | |
if jobmats[code] then return jobmats[code] else return nil end | |
end | |
--A messy function to get the item that should be used in the reaction. It's messy. | |
function getReagentItem(reaction,job,reagent_id) | |
local reagent_type = -1 | |
local reagent_subtype = -1 | |
local reagent = reaction.reagents[reagent_id] | |
--[[ | |
for _,v in ipairs(job.items or {}) do | |
vi = v.item | |
if (vi:getType() == reagent.item_type or reagent.item_type == -1) | |
and (vi:getSubtype() == reagent.item_subtype or reagent.item_subtype == -1) then | |
return vi | |
end | |
end | |
]]-- | |
for _,v in ipairs(job.items or {}) do | |
vi = v.item | |
vidx = v.job_item_idx | |
if (vi:getType() == reagent.item_type or reagent.item_type == -1) | |
and (vi:getSubtype() == reagent.item_subtype or reagent.item_subtype == -1) then | |
return vi | |
end | |
end | |
return nil | |
end | |
function getReagentByProduct(reaction,product_id) | |
local reagent_code = reaction.products[product_id].get_material.reagent_code | |
for i = 0, #reaction.reagents - 1, 1 do | |
v = reaction.reagents[i] | |
if v.code == reagent_code then | |
return i | |
end | |
end | |
return nil | |
end | |
function getReagentItemByProduct(reaction,job,product_id) | |
return getReagentItem(reaction,job,getReagentByProduct(product_id)) | |
end | |
function CreateSouljar(reaction, job, soul) | |
typ = reaction.products[0].item_type | |
subtyp = reaction.products[0].item_subtype | |
local emptySoulJar = getReagentItemByProduct(reaction, job, 0) | |
if emptySoulJar ~= nil then | |
mat_type = emptySoulJar.mat_type | |
mat_index = emptySoulJar.mat_index | |
end | |
local item = df['item_'..string.lower(df.item_type[typ])..'st']:new() | |
item:setSubtype(subtyp) | |
item.id = df.global.item_next_id | |
df.global.world.items.all:insert('#',item) | |
df.global.item_next_id = df.global.item_next_id+1 | |
item:setMaterial(mat_type) | |
item:setMaterialIndex(mat_index) | |
item:setMakerRace(df.global.ui.race_id) | |
if soul ~= nil then | |
--item:assignQuality(soul, skill) | |
item:setMaker(soul.unit_id) | |
InsertSoulAbstract(soul) | |
end | |
item:categorize(true) | |
item.flags.removed=true | |
item:moveToGround(worker.pos.x,worker.pos.y,worker.pos.z) | |
for _,v in ipairs(worker.job.current_job.general_refs or {}) do | |
if getmetatable(v) == 'general_ref_building_holderst' then | |
building = df.building.find(v.building_id) | |
--dfhack.items.moveToBuilding(item,building,0) | |
end | |
end | |
return item | |
end | |
function split(str, pat) | |
local t = {} -- NOTE: use {n = 0} in Lua-5.0 | |
local fpat = "(.-)" .. pat | |
local last_end = 1 | |
local s, e, cap = str:find(fpat, 1) | |
while s do | |
if s ~= 1 or cap ~= "" then | |
table.insert(t,cap) | |
end | |
last_end = e+1 | |
s, e, cap = str:find(fpat, last_end) | |
end | |
if last_end <= #str then | |
cap = str:sub(last_end) | |
table.insert(t, cap) | |
end | |
return t | |
end | |
function isAlliedSoul(soul) | |
local r = false | |
if soul.personality.civ_id == df.global.ui.civ_id then | |
r = true | |
end | |
return r | |
end | |
function setValid(subject,key,value) | |
if subject ~= nil then | |
subject[key] = value | |
end | |
end | |
function checkIfValidUnit(u,rules) | |
local ok = false | |
local bad = false | |
if hasClass(u,'DFHACK_SOULSHUFFLE_CANNOT_TARGET') then return false end | |
if rules['CREATURE'] ~= nil then | |
for i,v in ipairs(rules['CREATURE'] or {}) do | |
local racestring = v.race | |
local castestring = v.caste | |
local valid = v.valid | |
if df.global.world.raws.creatures.all[u.race].creature_id == racestring and | |
(df.global.world.raws.creatures.all[u.race].caste[u.caste].caste_id == castestring or castestring == 'NONE') then | |
if valid == true then ok = true | |
else return false end | |
end | |
end | |
end | |
if rules['CLASS'] ~= nil then | |
for i,v in ipairs(rules['CLASS'] or {}) do | |
local classstring = v.class | |
local valid = v.valid | |
if hasClass(u,classstring) then | |
if valid == true then ok = true | |
else return false end | |
end | |
end | |
end | |
if rules['SELF'] == true then ok = true end | |
for i,v in pairs(rules or {}) do | |
if type(i) == 'string' and type(v) == 'boolean' then | |
local strlower = i:lower() | |
local strupper = i:upper() | |
local flaglists = {u.flags1,u.flags2,u.flags3,df.global.world.raws.creatures.all[u.race].caste[u.caste].flags} | |
for listid,flaglist in ipairs((flaglists) or {}) do | |
for flagid,flagvalue in pairs(flaglist or {}) do | |
if flagid == strlower or flagid == strupper then | |
if rules[i] ~= nil and rules[i] == flagvalue then ok = true | |
elseif rules[i] ~= nil then return false end | |
end | |
end | |
end | |
end | |
end | |
if rules['ALIVE'] ~= nil and rules['ALIVE'] ~= dfhack.units.isDead(u) then ok = true | |
elseif rules['ALIVE'] ~= nil then return false end | |
if rules['CIV_UNIT'] ~= nil and rules['CIV_UNIT'] == (u.civ_id == df.global.ui.civ_id) then ok = true | |
elseif rules['CIV_UNIT'] ~= nil then return false end | |
if ok == true and bad == false then return true | |
else return false end | |
end | |
function getObjectSouls(object,rules,invalidSouls) | |
local souls = {} | |
if invalidSouls == nil then invalidSouls = {} end | |
if getmetatable(object) == 'unit' then | |
if rules['LIMIT'] ~= true or #object.status.souls > 1 then | |
if rules['SUB'] ~= true then | |
table.insert(souls,object.status.current_soul) | |
end | |
if rules['TOP'] ~= true then | |
for _,soul in ipairs(object.status.souls or {}) do | |
if object.status.current_soul.unit_id ~= soul.unit_id then | |
table.insert(souls,soul) | |
end | |
end | |
end | |
end | |
for i = #souls - 1, 0, -1 do | |
soul = souls[i] | |
if soul ~= nil then | |
if (rules['CIV_SOUL'] == true and isAlliedSoul(soul) == false) | |
or (rules['CIV_SOUL'] == false and isAlliedSoul(soul) == true) then | |
table.insert(invalidSouls,soul) | |
end | |
end | |
end | |
elseif getmetatable(object) == 'item_corpsest' then | |
else | |
local soul_id = object.maker | |
if soul_id ~= nil then | |
local soul = FindSoulAbstract(soul_id) | |
if soul ~= nil then | |
table.insert(souls,soul) | |
end | |
end | |
end | |
if invalidSouls ~= nil then | |
for i = #souls - 1, 0, -1 do | |
soul = souls[i] | |
for _,invalidsoul in ipairs(invalidSouls or {}) do | |
if soul ~= nil then | |
if soul.unit_id == invalidsoul.unit_id then | |
souls:erase(i) | |
end | |
end | |
end | |
end | |
end | |
return souls | |
end | |
function getObjectName(object) | |
if getmetatable(object) == 'unit' then | |
name = dfhack.TranslateName(object.name) | |
sexsym = sexSymbol(object.sex) | |
if name == '' then name = ((df.global.world.raws.creatures.all[object.race]).caste[object.caste].caste_name[0])..sexsym else | |
name = name .. ', ' .. ((df.global.world.raws.creatures.all[object.race]).caste[object.caste].caste_name[0])..sexsym | |
end | |
return name | |
elseif getmetatable(object) == 'item_corpsest' then | |
return 'Corpse' | |
else | |
return 'Soul Container' | |
end | |
end | |
function addExp(soul,skillId,amount,max_skill) | |
if max_skill == nil then max_skill = 20 end | |
--local skillId = df.job_skill[skillname] | |
--if skillId == nil or skillId == -1 then return nil end | |
local skill = df.unit_skill:new() | |
local foundSkill = false | |
doneLeveling = false | |
amountadded = 0 | |
while doneLeveling == false do | |
for k, soulSkill in ipairs(soul.skills) do | |
if soulSkill.id == skillId then | |
skill = soulSkill | |
foundSkill = true | |
break | |
end | |
end | |
if foundSkill then | |
-- Let's not train beyond the max skill | |
if skill.rating >= max_skill then | |
return false | |
end | |
skill.experience = skill.experience + amount | |
amount = 0 | |
if skill.experience > 100 * skill.rating + 500 then | |
skill.experience = skill.experience - (100 * skill.rating + 500) | |
skill.rating = skill.rating + 1 | |
elseif skill.experience < 0 then | |
if skill.rating == 0 then | |
amount = skill.experience * -1 | |
skill.experience = 0 | |
return amount | |
else | |
skill.experience = skill.experience + (100 * skill.rating + 500) | |
skill.rating = skill.rating - 1 | |
end | |
else | |
doneLeveling = true | |
end | |
else | |
skill.id = skillId | |
skill.experience = 0 | |
skill.rating = 0 | |
soul.skills:insert('#',skill) | |
end | |
end | |
return 0 | |
end | |
local function hasDuplicate(object,array) | |
for k, v in pairs(array) do | |
isDuplicate = true | |
for i, j in pairs(v) do | |
if object[i] ~= j then | |
isDuplicate = false | |
end | |
end | |
if isDuplicate == true then | |
return true | |
end | |
end | |
return false | |
end | |
local function insertIfNoDuplicate(object,array) | |
if hasDuplicate(object,array) == true then | |
return array | |
else | |
array:insert("#",object) | |
return array | |
end | |
end | |
--Not complete yet. | |
function CombineSouls(sourceSoul,targetSoul,opextra,efficiency) | |
if opextra == 'FUSE' then add = true end | |
if add == nil then add = false end | |
if efficiency == nil then efficiency = 100 end | |
local perc = efficiency/100 | |
for k,v in ipairs(sourceSoul.mental_attrs or {}) do | |
if add then | |
targetSoul.mental_attrs[k].value = targetSoul.mental_attrs[k].value + (v.value*perc) | |
else | |
targetSoul.mental_attrs[k].value = (targetSoul.mental_attrs[k].value * (1 - (perc / 2))) + (v.value * (perc / 2)) | |
end | |
end | |
for k,v in ipairs(sourceSoul.skills or {}) do | |
local experience = skillRatingToExperience(v.rating) + v.experience | |
if add == true then | |
addExp(targetSoul,v.id,experience*perc,v.rating) | |
else | |
addExp(targetSoul,v.id,experience*perc,v.rating,v.rating) | |
end | |
end | |
local sourceValues = getSoulValues(sourceSoul) | |
local targetValues = getSoulValues(targetSoul) | |
soulPowerRatio = sourceValues.attrs / targetValues.attrs | |
efficiency = efficiency * soulPowerRatio | |
perc = efficiency/100 | |
for i,v in ipairs(sourceSoul.preferences or {}) do | |
if math.random(100) < efficiency then targetSoul.preferences = insertIfNoDuplicate(v,targetSoul.preferences) end | |
end | |
for k,v in pairs(sourceSoul.personality.traits or {}) do | |
targetSoul.personality.traits[k] = (targetSoul.personality.traits[k] * (1 - (perc / 2))) + (v * (perc / 2)) | |
end | |
for i,v in ipairs(sourceSoul.personality.values or {}) do | |
local isDuplicate = false | |
for ti, tv in pairs(targetSoul.personality.values) do | |
if tv.type == v.type then | |
isDuplicate = true | |
tv.strength = (tv.strength * (1 - (perc / 2))) + (v.strength * (perc / 2)) | |
end | |
end | |
if isDuplicate == false then | |
v.strength = v.strength * (perc / 2) | |
targetSoul.personality.values:insert("#",v) | |
end | |
end | |
for i,v in ipairs(sourceSoul.personality.emotions or {}) do | |
if math.random(100) < efficiency then targetSoul.personality.emotions = insertIfNoDuplicate(v,targetSoul.personality.emotions) end | |
end | |
for i,v in ipairs(sourceSoul.personality.dreams or {}) do | |
if math.random(100) < efficiency then | |
local isDuplicate = false | |
for ti, tv in pairs(targetSoul.personality.dreams) do | |
if tv.type == v.type then | |
isDuplicate = true | |
end | |
end | |
if isDuplicate == false then | |
targetSoul.personality.dreams:insert("#",v) | |
end | |
end | |
end | |
if add then | |
targetSoul.personality.stress_level = targetSoul.personality.stress_level + (sourceSoul.personality.stress_level * perc) | |
else | |
targetSoul.personality.stress_level = (targetSoul.personality.stress_level * (1 - (perc / 2))) + (sourceSoul.personality.stress_level * (perc / 2)) | |
end | |
return targetSoul | |
end | |
local function deepCopySoul(original,copy) | |
for k, v in pairs(original) do | |
if getmetatable(v) ~= 'unit_skill' | |
and getmetatable(v) ~= 'unit_preference' | |
and getmetatable(v) ~= 'unit_personality' then | |
-- as before, but if we find a table, make sure we copy that too | |
if type(v) == 'table' or type(v) == 'userdata' then | |
if copy[k] == nil then copy:insert("#",v) | |
else v = deepCopySoul(v,copy[k]) end | |
else | |
copy[k] = v | |
end | |
end | |
end | |
return copy | |
end | |
--Not complete yet. | |
function SplitSoul(sourceSoul,opextra,efficiency) | |
targetSoul = df.unit_soul:new() | |
newSoul = deepCopySoul(sourceSoul,targetSoul) | |
targetSoul.unit_id=sourceSoul.unit_id | |
targetSoul.name:assign(sourceSoul.name) | |
targetSoul.race=sourceSoul.race | |
targetSoul.sex=sourceSoul.sex | |
targetSoul.caste=sourceSoul.caste | |
for k,v in pairs(sourceSoul.orientation_flags or {}) do | |
sourceSoul.orientation_flags[k] = sourceSoul.orientation_flags[k] | |
end | |
if opextra == 'COPY' then copy = true end | |
if copy == nil then copy = false end | |
if efficiency == nil then | |
if copy == true then efficiency = 100 else efficiency = 50 end | |
end | |
local perc = efficiency/100 | |
local target_perc = perc | |
local source_perc = 1 - perc | |
for k,v in ipairs(sourceSoul.mental_attrs or {}) do | |
if copy == true then | |
targetSoul.mental_attrs[k].value = sourceSoul.mental_attrs[k].value * perc | |
else | |
targetSoul.mental_attrs[k].value = sourceSoul.mental_attrs[k].value * target_perc | |
sourceSoul.mental_attrs[k].value = sourceSoul.mental_attrs[k].value * source_perc | |
end | |
end | |
for k,v in ipairs(sourceSoul.skills or {}) do | |
local experience = skillRatingToExperience(v.rating) + v.experience | |
if copy == true then | |
addExp(targetSoul,v.id,experience * perc,v.rating) | |
else | |
addExp(targetSoul,v.id,experience * target_perc,v.rating) | |
addExp(sourceSoul,v.id,-1*(experience * source_perc),v.rating) | |
end | |
end | |
for i=#sourceSoul.preferences-1,0,-1 do | |
v = sourceSoul.preferences[i] | |
targetSoul.preferences:insert("#",v) | |
end | |
for k,v in pairs(sourceSoul.personality.traits or {}) do | |
targetSoul.personality.traits[k] = v | |
end | |
for i=#sourceSoul.personality.values-1,0,-1 do | |
v = sourceSoul.personality.values[i] | |
targetSoul.personality.values:insert("#",v) | |
end | |
for i=#sourceSoul.personality.emotions-1,0,-1 do | |
v = sourceSoul.personality.emotions[i] | |
targetSoul.personality.emotions:insert("#",v) | |
end | |
for i=#sourceSoul.personality.dreams-1,0,-1 do | |
v = sourceSoul.personality.dreams[i] | |
targetSoul.personality.dreams:insert("#",v) | |
end | |
targetSoul.personality.civ_id = sourceSoul.personality.civ_id | |
targetSoul.personality.cultural_identity = sourceSoul.personality.cultural_identity | |
targetSoul.personality.stress_level = sourceSoul.personality.stress_level | |
targetSoul.unit_id = df.global.unit_next_id | |
df.global.unit_next_id=df.global.unit_next_id+1 | |
return {sourceSoul = sourceSoul, targetSoul = targetSoul} | |
end | |
function skillRatingToExperience(rating) | |
local total = 0 | |
for i=0, rating-1, 1 do | |
total = total + (100 * i) + 500 | |
end | |
return total | |
end | |
function getSoulValues (soul) | |
if soul == nil then return nil end | |
total_attribute_value = 0 | |
total_skill_value = 0 | |
for k,v in ipairs(soul.mental_attrs or {}) do | |
--local effectivevalue = (v.value + v.max_value)/2 | |
local effectivevalue = v.value | |
total_attribute_value = total_attribute_value + effectivevalue | |
end | |
local avg_attribute_value = total_attribute_value/#soul.mental_attrs | |
for k,v in ipairs(soul.skills or {}) do | |
local effectivevalue = skillRatingToExperience(v.rating) + v.experience | |
total_skill_value = total_skill_value + effectivevalue | |
end | |
return {attrs = avg_attribute_value, skills = total_skill_value, stress = soul.personality.stress_level} | |
end | |
function qualitySymbol(value) | |
if value >= 5 then return string.char(15) | |
elseif value == 4 then return string.char(240) | |
elseif value == 3 then return '*' | |
elseif value == 2 then return '+' | |
elseif value == 1 then return '-' | |
elseif value == 0 then return ' ' | |
else return '' end | |
end | |
function wearSymbol(value) | |
if value >= 3 then return 'XX' | |
elseif value == 2 then return 'X' | |
elseif value == 1 then return 'x' | |
else return '' end | |
end | |
function sexSymbol(value) | |
if value == 0 then return ', '..string.char(12) | |
elseif value == 1 then return ', '..string.char(11) | |
else return '' end | |
end | |
function getSoulItemProps (soul) | |
if soul == nil then return nil end | |
local values = getSoulValues(soul) | |
local attrvalue = values.attrs | |
local attrquality = 0 | |
if attrvalue > 2000 then attrquality = 5 | |
elseif attrvalue > 1300 then attrquality = 4 | |
elseif attrvalue > 1100 then attrquality = 2 | |
elseif attrvalue > 1000 then attrquality = 1 | |
else attrquality = 0 end | |
local expvalue = values.skills | |
local expquality = 0 | |
if expvalue > 30000 then expquality = 5 | |
elseif expvalue > 10000 then expquality = 4 | |
elseif expvalue > 7500 then expquality = 3 | |
elseif expvalue > 5000 then expquality = 2 | |
elseif expvalue > 2500 then expquality = 1 | |
else expquality = 0 end | |
local wear = 0 | |
if values.stress >= 100000 then wear = 1 | |
elseif values.stress >= 250000 then wear = 2 | |
elseif values.stress >= 500000 then wear = 3 end | |
return {decquality = expquality, quality = attrquality, wear = wear} | |
end | |
function getSoulName(soul) | |
itemprops = getSoulItemProps(soul) | |
qsym1 = qualitySymbol(itemprops.quality) | |
if qsym1 == ' ' then qsym1 = '' end | |
qsym2 = qualitySymbol(itemprops.decquality) | |
decsym1 = qsym2..string.char(174) | |
decsym2 = string.char(175)..qsym2 | |
sexsym = sexSymbol(soul.sex) | |
if qsym2 == '' then | |
decsym1 = '' | |
decsym2 = '' | |
end | |
wsym = wearSymbol(itemprops.wear) | |
name = dfhack.TranslateName(soul.name) | |
if name == '' then name = (df.global.world.raws.creatures.all[soul.race]).caste[soul.caste].caste_name[0]..' soul'..sexsym | |
else name = 'soul of ' .. name .. ', ' .. (df.global.world.raws.creatures.all[soul.race]).caste[soul.caste].caste_name[0] .. sexsym end | |
return(wsym..decsym1..qsym1..name..qsym1..decsym2..wsym) | |
end | |
function SoulToConstruct(reaction,unit,jobitems,constructrace,constructcaste,position,usemats,sourceSoul,validtargets) | |
if validtargets['CONSTRUCT'] == true then usemats = true else usemats = false end | |
if constructrace == nil then | |
constructrace = sourceSoul.race | |
constructcaste = sourceSoul.caste | |
elseif constructcaste == nil then | |
constructcaste = SoulForm(sourceSoul,constructrace).caste | |
end | |
local position = {x = unit.pos.x,y = unit.pos.y,z = unit.pos.z} | |
local construct = MakeConstruct(reaction,unit,jobitems,constructrace,constructcaste,position,usemats,sourceSoul) | |
return construct | |
end | |
-- ConstructCreature | |
--This is for the itemcorpses | |
eventful.onUnitDeath.bla=function(u_id) | |
local unit = df.unit.find(u_id) | |
if hasClass(unit,"DFHACK_CONSTRUCTCREATURE_ITEMCORPSE") then | |
local itemcorpse = nil | |
for i=0,#unit.corpse_parts-1,1 do | |
part=df.item.find(unit.corpse_parts[i]) | |
if getmetatable(part) ~= "item_corpsepiecest" then | |
itemcorpse = part | |
end | |
end | |
if itemcorpse ~= nil then | |
mat_type = unit.body.body_plan.materials.mat_type[0] | |
mat_index = unit.body.body_plan.materials.mat_index[0] | |
itemcorpse.mat_type = mat_type | |
itemcorpse.mat_index = mat_index | |
end | |
end | |
end | |
eventful.enableEvent(eventful.eventType.UNIT_DEATH,5) | |
function ConstructCreature(reaction, unit, in_items, in_reag, out_items, call_native) | |
--local skill = reaction.products[0].probability + dfhack.units.getEffectiveSkill(unit,reaction.skill) | |
--local race = reaction.products[0].mat_index --material index of output. If creature_mat:name:NONE, will be index of creature species | |
--local caste = reaction.products[0].product_dimension | |
call_native = false | |
local jobitems = unit.job.current_job.items | |
local jobmats = saveJobItemMats(reaction,jobitems) | |
codearray = split(reaction.code,' ') | |
if #codearray >= 2 then race = codearray[2] end | |
if #codearray >= 3 then caste = codearray[3] end | |
race = findRace(race) | |
print(race) | |
caste = findCaste(race,caste) | |
local pos={} | |
pos.x=unit.pos.x | |
pos.y=unit.pos.y | |
pos.z=unit.pos.z | |
local construct = MakeConstruct(reaction,unit,jobmats,race,caste,pos,true,soul) | |
if construct ~= nil then | |
call_native = true | |
end | |
end | |
function SoulShuffle(reaction, unit, in_items, in_reag, out_items, call_native) | |
call_native = false | |
local jobmats = saveJobItemMats(reaction,unit.job.current_job.items) | |
local operation = nil | |
local opextra = nil | |
local efficiency = nil | |
local attack = nil | |
local attack_success = nil | |
local validsources = {} | |
local validtargets = {} | |
local invert = false | |
local subject = nil | |
codearray = split(reaction.code,' ') | |
for i,code in ipairs(codearray or {}) do | |
if code == 'MOVE' or | |
code == 'SPLIT' or | |
code == 'COMBINE' or | |
code == 'REPLACE' or | |
code == 'SWAP' or | |
code == 'DESTROY' or | |
code == 'ASSIMILATE' then | |
operation = code | |
elseif code == 'ATTACK' then | |
if codearray[i+1] == tonumber(codearray[i+1]) then | |
attack = tonumber(codearray[i+1]) | |
if codearray[i+2] == tonumber(codearray[i+2]) then | |
attack_success = tonumber(codearray[i+2]) | |
i = i+2 | |
else | |
attack_success = 100 | |
i = i+1 | |
end | |
else | |
attack = 100 | |
attack_success = 50 | |
end | |
elseif code == 'MERGE' then | |
operation = 'COMBINE' | |
elseif code == 'FUSE' then | |
operation = 'COMBINE' | |
opextra = 'FUSE' | |
elseif code == 'COPY' then | |
operation = 'SPLIT' | |
opextra = 'COPY' | |
elseif code == 'NOT' then invert = true | |
elseif code == 'FROM' then | |
subject = validsources | |
elseif code == 'TO' then | |
subject = validtargets | |
elseif code == 'CONSTRUCT' or code == 'SPAWN' then | |
setValid(subject,code,not invert) | |
subject['RACE'] = codearray[i+1] | |
subject['CASTE'] = codearray[i+2] | |
i = i+2 | |
elseif code == 'CREATURE' then | |
if subject['CREATURE'] == nil then | |
subject['CREATURE'] = {} | |
end | |
table.insert(subject['CREATURE'],{race = codearray[i+1], caste = codearray[i+2], valid = not invert}) | |
i = i+2 | |
elseif code == 'CLASS' then | |
if subject['CLASS'] == nil then | |
subject['CLASS'] = {} | |
end | |
table.insert(subject['CLASS'],{class = codearray[i+1], valid = not invert}) | |
i = i+1 | |
elseif code == 'RANGE' then | |
subject['RANGE'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'Z' then | |
subject['Z'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'PERC' then | |
subject['PERC'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'STRESS_ADD' then | |
subject['STRESS_ADD'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'STRESS_PERC' then | |
subject['STRESS_PERC'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'MIN_ATTR' then | |
subject['MIN_ATTR'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'MAX_ATTR' then | |
subject['MAX_ATTR'] = tonumber(codearray[i+1]) | |
i = i+1 | |
elseif code == 'CAN_BREAK' then | |
if codearray[i+1] == tonumber(codearray[i+1]) then | |
subject['CAN_BREAK'] = tonumber(codearray[i+1]) | |
i = i+1 | |
else | |
if subject['MIN_ATTR'] ~= nil then | |
subject['CAN_BREAK'] = subject['MIN_ATTR'] / 2 | |
else | |
subject['CAN_BREAK'] = 50 | |
end | |
end | |
elseif code == 'CIV' then | |
setValid(subject,'CIV_UNIT',not invert) | |
setValid(subject,'CIV_SOUL',not invert) | |
lastkey = code | |
else --anything else | |
setValid(subject,code,not invert) | |
lastkey = code | |
end | |
if code ~= 'NOT' then invert = false end | |
end | |
--The operation should now be analyzed. Proceed for soul manipulation. | |
local sourceObject = nil | |
local sourceSoul = nil | |
local targetObject = nil | |
local targetSoul = nil | |
allUnits = df.global.world.units.active | |
radius = 10 | |
z = 0 | |
s_radius = nil | |
t_radius = nil | |
s_z = nil | |
t_z = nil | |
if validsources['RANGE'] ~= nil then s_radius = validsources['RANGE'] end | |
if validtargets['RANGE'] ~= nil then t_radius = validtargets['RANGE'] end | |
if validsources['Z'] ~= nil then s_z = validsources['Z'] end | |
if validtargets['Z'] ~= nil then t_z = validtargets['Z'] end | |
if s_radius == nil and t_radius ~= nil then s_radius = t_radius | |
elseif t_radius == nil and s_radius ~= nil then t_radius = s_radius | |
else | |
if t_radius == nil then t_radius = radius end | |
if s_radius == nil then s_radius = radius end | |
end | |
if s_z == nil and t_z ~= nil then s_z = t_z | |
elseif t_z == nil and s_z ~= nil then t_z = s_z | |
else | |
if t_z == nil then t_z = z end | |
if s_z == nil then s_z = z end | |
end | |
local sourceObjects = {} | |
local targetObjects = {} | |
--[[ | |
if validsources['ITEM'] == true then | |
item = getReagentItemByProduct(reaction,unit,0) | |
if item ~= nil then table.insert(sourceObjects, item) end | |
end | |
]]-- | |
for i=#allUnits-1,0,-1 do | |
u = allUnits[i] | |
if (closeBy(u,unit,s_radius,s_z) and validsources['SELF'] ~= true) or (u == unit and validsources['SELF'] ~= false) then | |
if checkIfValidUnit(u,validsources) then table.insert(sourceObjects, u) end | |
end | |
end | |
for i=#allUnits-1,0,-1 do | |
u = allUnits[i] | |
if (closeBy(u,unit,t_radius,t_z) and validtargets['SELF'] ~= true) or (u == unit and validtargets['SELF'] ~= false) then | |
if checkIfValidUnit(u,validtargets) and | |
not (hasClass(u,'DFHACK_SOULSHUFFLE_ONE_SOUL_LIMIT') and #u.status.souls ~= 0 and (operation == 'MOVE' or operation == 'SPLIT')) then --Hack to enforce one soul limit | |
table.insert(targetObjects, u) | |
end | |
end | |
end | |
needTargetObject = true | |
needTargetSoul = true | |
if operation == 'MOVE' or operation == 'SPLIT' then | |
needTargetSoul = false | |
end | |
if operation == 'DESTROY' or validtargets['CONSTRUCT'] == true or validtargets['SPAWN'] == true then | |
needTargetSoul = false | |
needTargetObject = false | |
end | |
--Get the souls to be manipulated | |
script.start(function() | |
finished1 = false | |
finished2 = false | |
finished3 = false | |
finished = false | |
while finished ~= true do | |
local opt_names = {} | |
local options = {} | |
for _,object in ipairs(sourceObjects or {}) do | |
if #getObjectSouls(object,validsources) > 0 then | |
table.insert(opt_names,getObjectName(object)) | |
table.insert(options,object) | |
end | |
end | |
local choiceok,choice=script.showListPrompt('Find souls', 'Select a source.', COLOR_LIGHTGREEN, opt_names) | |
if choice ~= nil then | |
sourceObject = options[choice] | |
while finished1 ~= true and finished ~= true do | |
local opt_names = {} | |
local options = {} | |
for _,soul in ipairs(getObjectSouls(sourceObject,validsources) or {}) do | |
table.insert(opt_names,getSoulName(soul)) | |
table.insert(options,soul) | |
end | |
local choiceok,choice=script.showListPrompt('Souls of '..dfhack.TranslateName(sourceObject.name), 'Select source soul.', COLOR_LIGHTGREEN, opt_names) | |
if choice ~= nil then | |
sourceSoul = options[choice] | |
if needTargetObject then | |
invalidSouls = {} | |
table.insert(invalidSouls,sourceSoul) | |
while finished2 ~= true and finished ~= true do | |
local opt_names = {} | |
local options = {} | |
for _,object in ipairs(targetObjects or {}) do | |
if validtargets['EMPTY'] ~= true or #getObjectSouls(object,validtargets,invalidSouls) == 0 then | |
table.insert(opt_names,getObjectName(object)) | |
table.insert(options,object) | |
end | |
end | |
if validtargets['SAME'] == true then | |
if validtargets['EMPTY'] ~= true or #getObjectSouls(sourceObject,validtargets,invalidSouls) == 0 then | |
table.insert(opt_names,getObjectName(sourceObject)) | |
table.insert(options,sourceObject) | |
end | |
end | |
--[[ | |
if validtargets['ITEM'] == true then | |
table.insert(opt_names,'Container') | |
table.insert(options,'product') | |
end | |
]]-- | |
local choiceok,choice=script.showListPrompt('Prepare for soul infusion', 'Select a target.', COLOR_LIGHTGREEN, opt_names) | |
if choice ~= nil then | |
targetObject = options[choice] | |
if needTargetSoul then | |
while finished3 ~= true and finished ~= true do | |
local opt_names = {} | |
local options = {} | |
for _,soul in ipairs(getObjectSouls(targetObject,validtargets) or {}) do | |
table.insert(opt_names,getSoulName(soul)) | |
table.insert(options,soul) | |
end | |
local choiceok,choice=script.showListPrompt('Souls of '..dfhack.TranslateName(targetObject.name), 'Select target soul.', COLOR_LIGHTGREEN, opt_names) | |
if choice ~= nil then | |
targetSoul = options[choice] | |
finished = true | |
else --Target soul was not selected | |
targetSoul = nil | |
finished3 = true | |
end | |
end | |
else | |
finished = true -- Don't need target soul | |
end | |
else --Target unit was not selected | |
targetObject = nil | |
finished2 = true | |
end | |
end | |
else | |
finished = true --Don't need target unit | |
end | |
else --Source soul was not selected | |
sourceSoul = nil | |
finished1 = true | |
end | |
end | |
else --Source unit was not selected | |
sourceObject = nil | |
finished = true | |
end | |
end | |
--Perform the operation | |
local success = false | |
if sourceObject ~= nil and sourceSoul ~= nil and (targetObject ~= nil or needTargetObject == false) and (targetSoul ~= nil or needTargetSoul == false) then | |
if validtargets['STRESS_PERC'] ~= nil then | |
SetStress(targetSoul,true,validtargets['STRESS_PERC']) | |
end | |
if validtargets['STRESS_ADD'] ~= nil then | |
SetStress(targetSoul,false,validtargets['STRESS_ADD']) | |
end | |
if validsources['STRESS_PERC'] ~= nil then | |
SetStress(sourceSoul,true,validsources['STRESS_PERC']) | |
end | |
if validsources['STRESS_ADD'] ~= nil then | |
SetStress(sourceSoul,false,validsources['STRESS_ADD']) | |
end | |
local canProceed = true | |
if attack ~= nil then | |
local allySoul = nil | |
local enemySoul = nil | |
if sourceSoul ~= nil then | |
if isAlliedSoul(sourceSoul) == true then allySoul = sourceSoul else enemySoul = sourceSoul end | |
elseif sourceObject ~= nil then | |
if isAlliedSoul(sourceObject.status.current_soul) == true then allySoul = sourceObject.status.current_soul else enemySoul = sourceObject.status.current_soul end | |
end | |
if targetSoul ~= nil then | |
if isAlliedSoul(targetSoul) == true then allySoul = targetSoul else enemySoul = targetSoul end | |
elseif targetObject ~= nil and targetObject.status.current_soul ~= nil then | |
if isAlliedSoul(targetObject.status.current_soul) == true then allySoul = targetObject.status.current_soul else enemySoul = targetObject.status.current_soul end | |
end | |
if enemySoul ~= nil then | |
if allySoul == nil then allySoul = unit.status.current_soul end | |
local r = SoulAttack(allySoul,enemySoul,attack,attack_success) | |
allySoul.personality.stress_level = r.sourceSoul.personality.stress_level | |
enemySoul.personality.stress_level = r.targetSoul.personality.stress_level | |
canProceed = false | |
if r.success == true then canProceed = true end | |
end | |
end | |
local message_displayed = false | |
if sourceSoul ~= nil then | |
local values = getSoulValues(sourceSoul) | |
if validsources['MIN_ATTR'] ~= nil then | |
if values.attrs < validsources['MIN_ATTR'] then | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul is too weak." , COLOR_RED, true) | |
message_displayed = true | |
canProceed = false | |
end | |
end | |
if validsources['MAX_ATTR'] ~= nil then | |
if values.attrs > validsources['MAX_ATTR'] then | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul is too strong." , COLOR_RED, true) | |
message_displayed = true | |
canProceed = false | |
end | |
end | |
if canProceed == true then | |
if validsources['CAN_BREAK'] ~= nil then | |
if SoulBreak(sourceSoul,validsources['CAN_BREAK']) then | |
if getmetatable(sourceObject) == 'unit' then RemoveSoul(sourceObject,sourceSoul) else RemoveSoulAbstract(sourceSoul) end | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul was destroyed." , COLOR_FUCHSIA, true) | |
message_displayed = true | |
canProceed = false | |
end | |
end | |
end | |
end | |
if targetSoul ~= nil then | |
local values = getSoulValues(targetSoul) | |
if validtargets['MIN_ATTR'] ~= nil then | |
if values.attrs < validtargets['MIN_ATTR'] then | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul is too weak." , COLOR_RED, true) | |
message_displayed = true | |
canProceed = false | |
end | |
end | |
if validtargets['MAX_ATTR'] ~= nil then | |
if values.attrs > validtargets['MAX_ATTR'] then | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul is too strong." , COLOR_RED, true) | |
message_displayed = true | |
canProceed = false | |
end | |
end | |
if canProceed == true then | |
if validtargets['CAN_BREAK'] ~= nil then | |
if SoulBreak(targetSoul,validtargets['CAN_BREAK']) then | |
if getmetatable(targetObject) == 'unit' then RemoveSoul(targetObject,targetSoul) else RemoveSoulAbstract(targetSoul) end | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul was destroyed." , COLOR_FUCHSIA, true) | |
message_displayed = true | |
canProceed = false | |
end | |
end | |
end | |
end | |
if canProceed == true then | |
if operation == 'MOVE' then | |
if getmetatable(sourceObject) == 'unit' then RemoveSoul(sourceObject,sourceSoul) else RemoveSoulAbstract(sourceSoul) end | |
if validtargets['CONSTRUCT'] == true or validtargets['SPAWN'] == true then | |
local construct = SoulToConstruct(reaction,unit,jobmats,validtargets['RACE'],validtargets['CASTE'],position,usemats,sourceSoul,validtargets) | |
success = true | |
else | |
if targetObject == 'container' then CreateSouljar(reaction,unit,sourceSoul) | |
elseif getmetatable(targetObject) == 'unit' then InsertSoul(targetObject,sourceSoul,validtargets['TOP']) end | |
success = true | |
end | |
elseif operation == 'SWAP' then | |
if getmetatable(sourceObject) == 'unit' then RemoveSoul(sourceObject,sourceSoul) else RemoveSoulAbstract(sourceSoul) end | |
if getmetatable(targetObject) == 'unit' then RemoveSoul(targetObject,targetSoul) else RemoveSoulAbstract(targetSoul) end | |
if getmetatable(sourceObject) == 'unit' then InsertSoul(sourceObject,targetSoul) else CreateSouljar(reaction,unit,targetSoul) end | |
if getmetatable(targetObject) == 'unit' then InsertSoul(targetObject,sourceSoul,validtargets['TOP']) else CreateSouljar(reaction,unit,sourceSoul) end | |
success = true | |
elseif operation == 'COMBINE' then | |
targetSoul = CombineSouls(sourceSoul,targetSoul,opextra,validsources['PERC']) | |
if targetSoul ~= nil then | |
if getmetatable(sourceObject) == 'unit' then RemoveSoul(sourceObject,sourceSoul) else RemoveSoulAbstract(sourceSoul) end | |
if getmetatable(targetObject) == 'unit' then setCurrentSoulValues(targetObject) end | |
success = true | |
end | |
elseif operation == 'SPLIT' then | |
newSouls = SplitSoul(sourceSoul,opextra,validsources['PERC']) | |
if newSouls['sourceSoul'] ~= nil and newSouls['targetSoul'] ~= nil then | |
if validtargets['CONSTRUCT'] == true or validtargets['SPAWN'] == true then | |
local construct = SoulToConstruct(reaction,unit,jobmats,validtargets['RACE'],validtargets['CASTE'],position,usemats,newSouls['targetSoul'],validtargets) | |
success = true | |
else | |
if getmetatable(targetObject) == 'unit' then InsertSoul(targetObject,newSouls['targetSoul'],validtargets['TOP']) end | |
success = true | |
end | |
end | |
elseif operation == 'REPLACE' then | |
if getmetatable(sourceObject) == 'unit' then RemoveSoul(sourceObject,sourceSoul) else RemoveSoulAbstract(sourceSoul) end | |
if getmetatable(targetObject) == 'unit' then RemoveSoul(targetObject,targetSoul) else RemoveSoulAbstract(targetSoul) end | |
if getmetatable(sourceObject) == 'unit' then InsertSoul(targetObject,sourceSoul) else CreateSouljar(reaction,unit,sourceSoul) end | |
elseif operation == 'DESTROY' then | |
if getmetatable(sourceObject) == 'unit' then RemoveSoul(sourceObject,sourceSoul) else RemoveSoulAbstract(sourceSoul) end | |
success = true | |
elseif operation == 'ASSIMILATE' then | |
targetSoul.civ_id = sourceSoul.civ_id | |
if getmetatable(targetObject) == 'unit' then setCurrentSoulValues(targetObject) end | |
success = true | |
end | |
if success == false then | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Soul manipulation failed." , COLOR_RED, true) | |
end | |
else | |
if message_displayed == false then | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": Could not overcome resistance." , COLOR_RED, true) | |
end | |
end | |
if success then | |
call_native = true | |
else | |
for _,v in ipairs(in_reag or {}) do | |
v.flags.PRESERVE_REAGENT = true | |
end | |
call_native = false | |
end | |
else | |
for _,v in ipairs(in_reag or {}) do | |
v.flags.PRESERVE_REAGENT = true | |
end | |
call_native = false | |
dfhack.gui.showAnnouncement( dfhack.TranslateName(unit.name).." cancels "..reaction.name..": No valid targets." , COLOR_RED, true) | |
end | |
end) | |
end | |
function SoulBreak(soul,prob) | |
local values = getSoulValues(soul) | |
if math.random(500000) < values.stress - 500000 - (values.attrs / prob) or math.random(prob*10) > values.attrs then | |
return true | |
else | |
return false | |
end | |
end | |
function SetStress(soul,perc,amount) | |
local add_amount = 0 | |
if perc == true then | |
add_amount = soul.personality.stress_level * (amount/100) | |
else | |
add_amount = amount | |
end | |
soul.personality.stress_level = soul.personality.stress_level + add_amount | |
return soul | |
end | |
function SoulAttack(sourceSoul,targetSoul,level,success_chance) | |
if level == nil then level = 100 end | |
if success_chance == nil then success_chance = 100 end | |
local sourceValues = getSoulValues(sourceSoul) | |
local targetValues = getSoulValues(targetSoul) | |
if sourceValues.stress < 1 then sourceValues.stress = 1 end | |
if targetValues.stress < 1 then targetValues.stress = 1 end | |
local sourcePower = sourceValues.attrs / sourceValues.stress | |
local targetPower = targetValues.attrs / targetValues.stress | |
local effectiveSourcePower = (sourcePower / targetPower) * level | |
local effectiveTargetPower = (targetPower / sourcePower) * level | |
print("sourcePower = " .. effectiveSourcePower) | |
print("targetPower = " .. effectiveTargetPower) | |
if math.random(success_chance) < effectiveSourcePower / effectiveTargetPower then | |
success = true | |
else | |
success = false | |
end | |
sourceSoul.personality.stress_level = sourceSoul.personality.stress_level + effectiveTargetPower * level | |
targetSoul.personality.stress_level = targetSoul.personality.stress_level + effectiveSourcePower * level | |
print("sourceStress = " .. sourceSoul.personality.stress_level) | |
print("targetStress = " .. targetSoul.personality.stress_level) | |
return {sourceSoul = sourceSoul, targetSoul = targetSoul, success = success} | |
end | |
function MakeConstruct(reaction,worker,jobmats,race,caste,pos,usemats,soul) | |
race = findRace(race) | |
caste = findCaste(race,caste) | |
if caste == nil or caste < 0 then | |
caste = 0 | |
end | |
if race == -1 then | |
print("Error: Creature not found...") | |
end | |
if usemats == true then | |
--Handle colors | |
for i,v in ipairs(df.global.world.raws.creatures.all[race].caste[0].creature_class) do | |
if string.starts(v.value, 'DFHACK_CONSTRUCTCREATURE_CASTECOLORS') then | |
firstmat = df.global.world.raws.creatures.all[race].material[0] | |
code = firstmat.id | |
local r_mat = nil | |
if jobmats[code] then r_mat = jobmats[code] end | |
if r_mat ~= nil then | |
local r_mat_color_0=r_mat.material.basic_color[0] | |
local r_mat_color_1=r_mat.material.basic_color[1] | |
local c = r_mat_color_0 .. r_mat_color_1 | |
if c == '00' then caste = 0 | |
elseif c == '10' then caste = 1 | |
elseif c == '20' then caste = 2 | |
elseif c == '30' then caste = 3 | |
elseif c == '40' then caste = 4 | |
elseif c == '50' then caste = 5 | |
elseif c == '60' then caste = 6 | |
elseif c == '70' then caste = 7 | |
elseif c == '01' then caste = 8 | |
elseif c == '11' then caste = 9 | |
elseif c == '21' then caste = 10 | |
elseif c == '31' then caste = 11 | |
elseif c == '41' then caste = 12 | |
elseif c == '51' then caste = 13 | |
elseif c == '61' then caste = 14 | |
elseif c == '71' then caste = 15 end | |
break | |
else | |
print('Could not find valid reagent item for color caste system.') | |
end | |
end | |
end | |
end | |
race_name = df.creature_raw.find(race).name[0] | |
caste_name = df.creature_raw.find(race).caste[caste].caste_name[0] | |
--Create a new unit | |
local u=PlaceUnit(race,caste,nil,pos) | |
u.civ_id = worker.civ_id | |
if soul ~= nil then | |
InsertSoul(u,soul) | |
u.name:assign(soul.name) | |
elseif not string.find(reaction.code,'NOSOUL') then | |
local castedata=getCaste(race,caste) | |
soul = CreateSoul(u,castedata) | |
InsertSoul(u,soul) | |
end | |
if u then | |
if usemats == true then | |
for i=0,#u.body.body_plan.materials.mat_type-1,1 do | |
local matbase = dfhack.matinfo.decode(u.body.body_plan.materials.mat_type[i],u.body.body_plan.materials.mat_index[i]) | |
local code = '' | |
if matbase.mode == 'creature' or matbase.mode == 'plant' then | |
code = matbase.material.id | |
elseif matbase.mode == 'inorganic' then | |
code = matbase.inorganic.id | |
end | |
local r_item = getReagentItemByCode(reaction,jobmats,code) | |
r_mat = nil | |
if jobmats[code] then r_mat = jobmats[code] end | |
if r_mat ~= nil then | |
local r_mat_index=r_mat.index | |
local r_mat_type=r_mat.type | |
u.body.body_plan.materials.mat_index[i] = r_mat_index | |
u.body.body_plan.materials.mat_type[i] = r_mat_type | |
end | |
end | |
baseMaterial = dfhack.matinfo.decode(u.body.body_plan.materials.mat_type[0],u.body.body_plan.materials.mat_index[0]) | |
if baseMaterial ~= nil then | |
r_mat_name = baseMaterial.material.state_name.Solid | |
r_mat_adj=baseMaterial.material.state_adj.Solid | |
u.custom_profession=r_mat_adj .. ' ' .. caste_name | |
end | |
end | |
return u | |
else | |
return nil | |
end | |
end | |
--This part is mostly copied from warmist's spawnunit script | |
function getCaste(race_id,caste_id) | |
local cr=df.creature_raw.find(race_id) | |
return cr.caste[caste_id] | |
end | |
function genBodyModifier(body_app_mod) | |
local a=rando:random(#body_app_mod.ranges-2)-1 | |
if a<0 then a=a+(-2*a) end | |
return rando:random(body_app_mod.ranges[a+1]-body_app_mod.ranges[a])+body_app_mod.ranges[a] | |
end | |
function getBodySize(caste,time) | |
--todo real body size... | |
return caste.body_size_1[#caste.body_size_1-1] --returns last body size | |
end | |
function genAttribute(array) | |
local a=rando:random(#array-2)-1 | |
if a<0 then a=a+(-2*a) end | |
return rando:random(array[a+1]-array[a])+array[a] | |
end | |
function norm() | |
return math.sqrt((-2)*math.log(rando:drandom()))*math.cos(2*math.pi*rando:drandom()) | |
end | |
function normalDistributed(mean,sigma) | |
return mean+sigma*norm() | |
end | |
function clampedNormal(min,median,max) | |
local val=normalDistributed(median,math.sqrt(max-min)) | |
if val<min then return min end | |
if val>max then return max end | |
return val | |
end | |
function CreateSoul(unit,caste) | |
local tmp_soul=df.unit_soul:new() | |
tmp_soul.unit_id=unit.id | |
tmp_soul.name:assign(unit.name) | |
tmp_soul.race=unit.race | |
tmp_soul.sex=unit.sex | |
tmp_soul.caste=unit.caste | |
--todo skills,preferences,traits. | |
local attrs=caste.attributes | |
for k,v in pairs(attrs.ment_att_range) do | |
local max_percent=attrs.ment_att_cap_perc[k]/100 | |
local cvalue=genAttribute(v) | |
tmp_soul.mental_attrs[k]={value=cvalue,max_value=cvalue*max_percent} | |
end | |
for k,v in pairs(tmp_soul.traits) do | |
local min,mean,max | |
min=caste.personality.a[k] | |
mean=caste.personality.b[k] | |
max=caste.personality.c[k] | |
tmp_soul.traits[k]=clampedNormal(min,mean,max) | |
end | |
return tmp_soul | |
end | |
function CreateUnit(race_id,caste_id) | |
local race=df.creature_raw.find(race_id) | |
if race==nil then error("Invalid race_id") end | |
local caste=getCaste(race_id,caste_id) | |
local unit=df.unit:new() | |
unit.race=race_id | |
unit.caste=caste_id | |
unit.id=df.global.unit_next_id | |
df.global.unit_next_id=df.global.unit_next_id+1 | |
if caste.misc.maxage_max==-1 then | |
unit.relations.old_year=-1 | |
else | |
unit.relations.old_year=df.global.cur_year-math.ceil(caste.misc.maxage_min/2)+rando:random(caste.misc.maxage_max-caste.misc.maxage_min)+caste.misc.maxage_min | |
end | |
unit.sex=caste.gender | |
local body=unit.body | |
body.body_plan=caste.body_info | |
local body_part_count=#body.body_plan.body_parts | |
local layer_count=#body.body_plan.layer_part | |
--components | |
unit.relations.birth_year=df.global.cur_year-math.ceil(caste.misc.maxage_min/2) | |
--unit.relations.birth_time=?? | |
--unit.relations.old_time=?? --TODO add normal age | |
local cp=body.components | |
cp.body_part_status:resize(body_part_count) | |
cp.numbered_masks:resize(#body.body_plan.numbered_masks) | |
for num,v in ipairs(body.body_plan.numbered_masks) do | |
cp.numbered_masks[num]=v | |
end | |
cp.layer_status:resize(layer_count) | |
cp.layer_wound_area:resize(layer_count) | |
cp.layer_cut_fraction:resize(layer_count) | |
cp.layer_dent_fraction:resize(layer_count) | |
cp.layer_effect_fraction:resize(layer_count) | |
local attrs=caste.attributes | |
for k,v in pairs(attrs.phys_att_range) do | |
local max_percent=attrs.phys_att_cap_perc[k]/100 | |
local cvalue=genAttribute(v) | |
unit.body.physical_attrs[k]={value=cvalue,max_value=cvalue*max_percent} | |
--unit.body.physical_attrs:insert(k,{new=true,max_value=genMaxAttribute(v),value=genAttribute(v)}) | |
end | |
body.blood_max=getBodySize(caste,0) --TODO normal values | |
body.blood_count=body.blood_max | |
body.infection_level=0 | |
unit.status2.body_part_temperature:resize(body_part_count) | |
for k,v in pairs(unit.status2.body_part_temperature) do | |
unit.status2.body_part_temperature[k]={new=true,whole=10067,fraction=0} | |
end | |
-------------------- | |
local stuff=unit.enemy | |
stuff.body_part_878:resize(body_part_count) -- all = 3 | |
stuff.body_part_888:resize(body_part_count) -- all = 3 | |
stuff.body_part_relsize:resize(body_part_count) -- all =0 | |
stuff.anon_4 = -1 | |
stuff.anon_5 = -1 | |
stuff.anon_6 = -1 | |
--TODO add correct sizes. (calculate from age) | |
local size=caste.body_size_2[#caste.body_size_2-1] | |
body.size_info.size_cur=size | |
body.size_info.size_base=size | |
body.size_info.area_cur=math.pow(size,0.666) | |
body.size_info.area_base=math.pow(size,0.666) | |
body.size_info.area_cur=math.pow(size*10000,0.333) | |
body.size_info.area_base=math.pow(size*10000,0.333) | |
stuff.were_race=race_id | |
stuff.were_caste=caste_id | |
stuff.normal_race=race_id | |
stuff.normal_caste=caste_id | |
stuff.body_part_8a8:resize(body_part_count) -- all = 1 | |
stuff.body_part_base_ins:resize(body_part_count) | |
stuff.body_part_clothing_ins:resize(body_part_count) | |
stuff.body_part_8d8:resize(body_part_count) | |
unit.recuperation.healing_rate:resize(layer_count) | |
--appearance | |
local app=unit.appearance | |
app.body_modifiers:resize(#caste.body_appearance_modifiers) --3 | |
for k,v in pairs(app.body_modifiers) do | |
app.body_modifiers[k]=genBodyModifier(caste.body_appearance_modifiers[k]) | |
end | |
app.bp_modifiers:resize(#caste.bp_appearance.modifier_idx) --0 | |
for k,v in pairs(app.bp_modifiers) do | |
app.bp_modifiers[k]=genBodyModifier(caste.bp_appearance.modifiers[caste.bp_appearance.modifier_idx[k]]) | |
end | |
--app.unk_4c8:resize(33)--33 | |
app.tissue_style:resize(#caste.bp_appearance.style_part_idx) | |
app.tissue_style_civ_id:resize(#caste.bp_appearance.style_part_idx) | |
app.tissue_style_id:resize(#caste.bp_appearance.style_part_idx) | |
app.tissue_style_type:resize(#caste.bp_appearance.style_part_idx) | |
app.tissue_length:resize(#caste.bp_appearance.style_part_idx) | |
app.genes.appearance:resize(#caste.body_appearance_modifiers+#caste.bp_appearance.modifiers) --3 | |
app.genes.colors:resize(#caste.color_modifiers*2) --??? | |
app.colors:resize(#caste.color_modifiers)--3 | |
df.global.world.units.all:insert("#",unit) | |
df.global.world.units.active:insert("#",unit) | |
--todo set weapon bodypart | |
local num_inter=#caste.body_info.interactions | |
--unit.curse.anon_5:resize(num_inter) | |
return unit | |
end | |
function findRace(name) | |
if tonumber(name) ~= nil then return name end | |
for k,v in pairs(df.global.world.raws.creatures.all) do | |
if v.creature_id==name then | |
return k | |
end | |
end | |
return nil | |
end | |
function findCaste(race,name) | |
if tonumber(name) ~= nil then return name end | |
for k,v in pairs(df.global.world.raws.creatures.all[race].caste) do | |
if v.caste_id==name then | |
return k | |
end | |
end | |
return nil | |
end | |
function PlaceUnit(race,caste,name,position) | |
local pos=position or copyall(df.global.cursor) | |
if pos.x==-30000 then | |
qerror("Point your pointy thing somewhere") | |
end | |
race=findRace(race) | |
local u=CreateUnit(race,tonumber(caste) or 0) | |
u.pos:assign(pos) | |
if name then | |
u.name.first_name=name | |
u.name.has_name=true | |
end | |
u.civ_id=df.global.ui.civ_id | |
local desig,ocupan=dfhack.maps.getTileFlags(pos) | |
if ocupan.unit then | |
ocupan.unit_grounded=true | |
u.flags1.on_ground=true | |
else | |
ocupan.unit=true | |
end | |
createNemesis(u) | |
return u | |
end | |
function createFigure(trgunit) | |
local hf=df.historical_figure:new() | |
hf.id=df.global.hist_figure_next_id | |
hf.race=trgunit.race | |
hf.caste=trgunit.caste | |
df.global.hist_figure_next_id=df.global.hist_figure_next_id+1 | |
hf.name.first_name=trgunit.name.first_name | |
hf.name.has_name=trgunit.name.has_name | |
df.global.world.history.figures:insert("#",hf) | |
return hf | |
end | |
function createNemesis(trgunit) | |
local id=df.global.nemesis_next_id | |
local nem=df.nemesis_record:new() | |
nem.id=id | |
nem.unit_id=trgunit.id | |
nem.unit=trgunit | |
nem.flags:resize(1) | |
nem.flags[4]=true | |
nem.flags[5]=true | |
nem.flags[6]=true | |
nem.flags[7]=true | |
nem.flags[8]=true | |
nem.flags[9]=true | |
--[[for k=4,8 do | |
nem.flags[k]=true | |
end]] | |
df.global.world.nemesis.all:insert("#",nem) | |
df.global.nemesis_next_id=id+1 | |
trgunit.general_refs:insert("#",{new=df.general_ref_is_nemesisst,nemesis_id=id}) | |
trgunit.flags1.important_historical_figure=true | |
local gen=df.global.world.worldgen | |
nem.save_file_id=gen.next_unit_chunk_id; | |
gen.next_unit_chunk_id=gen.next_unit_chunk_id+1 | |
gen.next_unit_chunk_offset=gen.next_unit_chunk_offset+1 | |
--[[ local gen=df.global.world.worldgen | |
gen.next_unit_chunk_id | |
gen.next_unit_chunk_offset | |
]] | |
nem.figure=createFigure(trgunit) | |
end | |
function SoulForm(soul,race) | |
local race = findRace(race) | |
local caste = nil | |
if race == nil or race < 0 then | |
race = soul.race | |
caste = soul.caste | |
else | |
castes = df.global.world.raws.creatures.all[race].caste | |
soul_race = df.global.world.raws.creatures.all[soul.race].race_id | |
soul_caste = df.global.world.raws.creatures.all[soul.race].caste[soul.caste].caste_id | |
soul_classes = df.global.world.raws.creatures.all[soul.race].caste[soul.caste].creature_class | |
match_level = 0 | |
caste = math.random(#castes)-1 | |
for i = 0, #castes - 1, 1 do | |
v = castes[i] | |
this_match_level = 0 | |
if string.find(v.caste_id,soul_race) then | |
if string.find(v.caste_id,soul_caste) then | |
if v.caste_id == soul_race..' '..soul_caste or v.caste_id == soul_race..'_'..soul_caste then | |
this_match_level = 7 | |
break | |
else | |
this_match_level = 6 | |
end | |
else | |
if v.caste_id == soul_caste then | |
this_match_level = 5 | |
else | |
this_match_level = 4 | |
end | |
end | |
else | |
for _,class in ipairs(soul_classes or {}) do | |
if class.value == v.caste_id then | |
this_match_level = 3 | |
end | |
end | |
end | |
if this_match_level == 0 then | |
if ((v.caste_id == 'DEFAULT_FEMALE' and soul.sex == 0) or (v.caste_id == 'DEFAULT_MALE' and soul.sex == 1)) then | |
this_match_level = 2 | |
elseif v.caste_id == 'DEFAULT' then | |
this_match_level = 1 | |
end | |
end | |
if this_match_level > match_level then | |
match_level = this_match_level | |
caste = i | |
end | |
end | |
end | |
return {race = race, caste = caste} | |
end | |
function FindDead(reaction, unit, in_items, in_reag, out_items, call_native) | |
end | |
function TestFunction(reaction, unit, in_items, in_reag, out_items, call_native) | |
print('test') | |
end | |
function AddUsesString (viewscreen,inp_string,indent) | |
local string = df.new("string") | |
string.value = tostring(inp_string) | |
if not indent then indent = 0 end | |
viewscreen.entry_ref:insert('#', nil) | |
viewscreen.entry_indent:insert('#', indent) | |
viewscreen.unk_34:insert('#', nil) | |
viewscreen.entry_string:insert('#', string) | |
end | |
function string.starts(String,Start) | |
return string.sub(String,1,string.len(Start))==Start | |
end | |
dfhack.onStateChange.loadSoulShuffle = function(code) | |
if code==SC_MAP_LOADED then | |
--Load all reactions | |
for i,reaction in ipairs(df.global.world.raws.reactions) do | |
if string.starts(reaction.code,'LUA_HOOK_'..prefix..'_FIND_DEAD') then | |
eventful.registerReaction(reaction.code,FindDead) | |
registered_reactions = true | |
elseif string.starts(reaction.code,'LUA_HOOK_'..prefix..'_OTHER') then | |
eventful.registerReaction(reaction.code,TestFunction) | |
registered_reactions = true | |
elseif string.starts(reaction.code,'LUA_HOOK_'..prefix) then | |
eventful.registerReaction(reaction.code,SoulShuffle) | |
registered_reactions = true | |
elseif string.starts(reaction.code,'LUA_HOOK_CONSTRUCTCREATURE') then | |
eventful.registerReaction(reaction.code,ConstructCreature) | |
registered_reactions = true | |
end | |
end | |
print('SoulShuffle: Loaded.') | |
print('ConstructCreature: Loaded.') | |
elseif code == SC_VIEWSCREEN_CHANGED and dfhack.isWorldLoaded() then | |
if dfhack.gui.getCurViewscreen()._type == df.viewscreen_itemst then | |
local scr = dfhack.gui.getCurViewscreen() | |
--AddUsesString(scr,'babababababab adfasdf') | |
end | |
end | |
end | |
--Initial call | |
if dfhack.isMapLoaded() then | |
dfhack.onStateChange.loadSoulShuffle(SC_MAP_LOADED) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment