Tutorial - Corruption mechanic

Post here about all aspects of D2 mod making whether it's information, problems or whatever. Please specify whether your post is relating to Classic D2 or the Expansion.

Moderator: Nizari

jnarical
Posts: 1
Joined: Tue Apr 18, 2023 7:04 am
Russia

Tutorial - Corruption mechanic

Post by jnarical » Tue Apr 18, 2023 9:21 pm

Image

Goals:
  • Create new item, "Orb of Corruption" (OOC for short)
  • Make it look like red version of Mephisto soulstone, with purple title.
  • Make it available from vendors (for testing purposes)
  • Cubing it with any item gives next outcomes:
  • 25% chance that item won't be changed
  • 25% chance that item obtain sockets, up to maximum
  • 25% chance that item will turn into random rare
  • 25% chance that item will obtain random corruption property from the list
  • After cubing, item obtain red-colored "Corrupted" property
  • Item with such property cannot be cubed with OOC anymore
Also:
  • List of corruption properties should be different for any defined group of items
  • It should be easy configurable in terms of properties count and probabilistic weights
Files you'll need to edit:
  • patchstring.tbl - colored strings to display in game
  • AutoMagic.txt - change the appearence of Mephisto soulstone to red (for OOC only, original soulstone will stay intact)
  • ItemStatCost.txt - define two new properties types
  • Properties.txt - define two new properties, based on those types
  • ItemTypes.txt - define new item "family" for orbs
  • Misc.txt - define "Orb of Corruption" item
  • CubeMain.txt - add recipes for OOC. A whole lot of them...
Part 1: Adding new strings

In any convenient TBL editor open patchstring.tbl and add 4 new keys:

Code: Select all

ooc = "\purple;Orb of Corruption"
ooc_desc = "Changes an item with unpredictable outcome, making it \red;Corrupted\white;"
corrupted = "\red;Corrupted"
corrupt_value = "\green;Hidden corruption"
Attention: if you prefer AFJ TBL Edit, you'll need v1.12u - older versions work incorrectly with colors

Part 2: Adding Orb of Corruption item

In ItemTypes.txt create new line:

Code: Select all

ItemType:  Orbs of Corruption
Code:      oocs
Equiv1:    misc
Normal:    1
Rarity:    3
StorePage: misc
*eol:      0
Other fields should be blank or zero, similar to other lines

Now in Misc.txt you can add Orb of Corruption itself:

Code: Select all

name:  Orbs of Corruption
*name:  Orbs of Corruption
rarity:  1
spawnable:  1
nodurability:  1
cost:  100
code:  ooc              - Here's our new item code
namestr:  ooc           - string ID which we created in .tbl file
flippyfile:  flpmss     - image of Meph soulstone laying on the ground
invfile:  invmss        - ...and in inventory
type:  oocs             - our new type from ItemTypes.txt
dropsound:  item_gem
dropsfxframe:  12
usesound:  item_gem
spelldesc:  1           - means we want to show text description for OOC
spelldescstr:  oocdesk  - name of our description string in .tbl file
CharsiMin:  1
CharsiMax: 1            - we want Charsi to sell OOCs
Transform:  8
InvTrans:  8            - these two fields define palette number for recoloring soulstone. Palette defines which color should be substituted. 
PermStoreItem: 1        - we want Charsi to sell OOCs infinitely
*eol:      0
Other fields should be blank, zero, or similar to other lines

Next, open AutoMagic.txt and append new string in the end:

Code: Select all

Name:  OOC recolor
version:  100
spawnable:  1
rare:                   - here's blank field
level:  1
levelreq:  0
frequency:  1
group:  306             - here YOU should find out what's next free group number!!!
transform:  1           - tell the game that we want to recolor our item
transformcolor:  cred   - substitute colors from palette to this particular color (bright red)
itype1:  oocs           - 4-letter code! Not ooc !
*eol:      0
Other fields are basically blank

We're almost done here. Remember group number which you came up to? Go to Misc.txt again, find "auto prefix" field in your OOC line and enter it there (in my case - 306). Done.
At this point it OOC should start to appear in-game.

Part 3: How it all works?

The idea here's very similar to "Spawning A Random Item From A Recipe" tutorial. We add two new properties - one of boolean type, means that item is corrupted. Second one is hidden. It can take different values from range, and is used in corrupting recipes. Game checks recipes in top-down direction. Here's how it all works:

Code: Select all

== Recipe1 ==
description:  Amulet + OOC, hidden_corruption=1 ->  +1 skills
op:  18
param:  XXX               - ID of hidden property!!! Should be valid number
value: 1
numinputs:  2
input1:  amu
input2:  ooc
output:  useitem
mod1:  allskills          - with 1/1 in min/max, we add +1 to all skills to our amulet
mod2:  item_corrupted     - we set "corrupted" property to 1
mod3:  hidden_corruption  - we substract some number to somewhat reset that stat

== Recipe2 ==
description:  Amulet + OOC, hidden_corruption=2 ->  cannot be frozen
op:  18
param:  XXX
value: 2
etc. For every possible hidden stat value - we define separate recipe.

Code: Select all

== Final Recipe ==
description:  Non-corrupted amulet + OOC -> Non-corrupted amulet with random hidden stat + OOC
op:  18
param:  YYY           - ID of visible boolean property!!! Should be valid number
value: 0
What does op code 18 do actually? It takes property with ID in "param" field from item in "input1" field (hidden_corruption in our case), and compares it's value to number in "value" field, using function number from "op" field. Op code 18 means "not equal", so in Recipe #1 we take value of hidden property, and compare if it's NOT EQUAL to 1. Recipe fails if it's not, and game checks next recipe in order.

When you transmuting new item with OOC, the game searches recipes top-down, looking for first working match. But since hidden stat isn't set initially, all recipes where it should be equal to particular number - fail. Last recipe checks, if "corrupted" property is set. And initially it's not. So recipe adds new hidden property, with random value in set range. So, first click on "Transmute" does nothing visible, but item gets new hidden property. After second "Transmute" game finds where value from recipe equals hidden property on item, and uses that recipe:
  • adds desired magic property, like +1 all skills in first recipe
  • sets "item_orrupted" property on item to 1
  • substracts some number from hidden_corruption to make it zero or negative.
Since then, no working corrupting recipes left for that item. It's hidden_corruption property can't match to 1, 2, 3... etc. numbers in recipes, and final recipe fails too because "corrupted" flag already set.

Part 4: Adding new properties for corruption

Create two new lines in ItemStatCost.txt:

Code: Select all

Stat:  item_corrupted
ID:         <<<< Here you should set next available number. It's your stat ID, which will be used in final recipe.
Signed:  1
Send Bits:  2           - it should be slightly more then "Save Bits", according to another tutorial
Add:  0
Multiply:  0
Divide:  1024
Save Bits:  1           - for boolean value we need only one bit of memory storage
Save Add:  0
descpriority:  255      - if you want "Corrupted" property was topmost on the item
descfunc:  3            - how to combine string from .tbl file with property value
descval:  0             - we don't want to output numerical value of "item_corrupted" property, we need just string from .tbl
descstrpos:  corrupted  - ID of our string in .tbl file, big red "Corrupted" text
*eol:      0

Code: Select all

Stat:  hidden_corruption
ID:         <<<< Here you should set next available number. It's your stat ID, which will be used in corruption recipes.
Signed:  1
Send Bits:  8              - it should be slightly more then "Save Bits", according to another tutorial
Add:  0
Multiply:  0
Divide:  1024
Save Bits:  6              - with 6 bits, we have 2^6-1=63 possible values for our possible corruption outcomes. Every increase Save Bits by 1 doubles that number.
Save Add:  0
descpriority:  254         - slightly lower then "Corrupted" text
descfunc:  3
descval:  1                - here we actually want to display our hidden property! It'll help us with debugging, Don't forget to remove all desc* fields here when you're done with recipes.
descstrpos:  corrupt_value - ID of our string in .tbl file, green text
*eol:      0
Other fields should be blank or zero, similar to other lines

Next file - Properties.txt:

Code: Select all

code =  corrupt
done* =  1
func1 =  1
stat1 =  item_corrupted
*eol:  0

Code: Select all

code =  corrupt_num
done* =  1
func1 =  1
stat1 =  hidden_corruption
*eol:  0
Part 5: Weighting probabilities

Main point is - choose even number of corruption mods! For example, let's take 6 mods:

Code: Select all

hidden_corruption = 1 -> +all skills
hidden_corruption = 2 -> +str
hidden_corruption = 3 -> +dex
hidden_corruption = 4 -> +vit
hidden_corruption = 5 -> +all res
hidden_corruption = 6 -> +health
We can set mod_chances inside those recipes, so 6 possible outcomes already include "failed recipes" outcomes too. 25% for useful outcomes + 25% for failed ones equals 6 different values in hidden_corruption. To add 25% of creating sockets (with 100% mod_chance) - we should add another 3 values, same for 25% of changing item type to Rare. So, item_corruption should be generated in range 1-12. And same number (12) should be substracted from hidden_corruption in each recipe at the end. Make mod_chances 50% and it will create 50/50 useful and failed recipe rolls.

Conclusion

Here's part of my CubeMain.txt - it's not finished yet, I want different set of properties for boots, gloves, helms and belts.
https://drive.google.com/file/d/1R2Tfth ... sp=sharing

TIP: If you don't know how to add some property to your recipe - check uniqueitems.txt for reference.

This tutorial would've been impossible without:

Return to “General Mod Making”