From 482794473972a1265308fcb89069066483bbdda0 Mon Sep 17 00:00:00 2001 From: Starfall Date: Wed, 7 Feb 2024 17:54:47 -0600 Subject: dropcalc: Median XL .txt parser Was intended to eventually become a drop calculator. Might work for other Diablo II mods without changes, I don't think there are any MXL-specific fields involved in this. Abandoned due to lack of organizational structure on the project and ... creative differences with one of the lead devs. --- dropcalc/.gitignore | 6 ++ dropcalc/LICENSE | 13 ++++ dropcalc/Pipfile | 12 +++ dropcalc/Pipfile.lock | 147 +++++++++++++++++++++++++++++++++++ dropcalc/README.md | 20 +++++ dropcalc/create_db.py | 69 ++++++++++++++++ dropcalc/dropcalc-notes | 61 +++++++++++++++ dropcalc/mxl_types/__init__.py | 15 ++++ dropcalc/mxl_types/base.py | 34 ++++++++ dropcalc/mxl_types/dao.py | 13 ++++ dropcalc/mxl_types/item_ratio.py | 39 ++++++++++ dropcalc/mxl_types/item_type.py | 32 ++++++++ dropcalc/mxl_types/set.py | 15 ++++ dropcalc/mxl_types/treasure_class.py | 46 +++++++++++ dropcalc/mxl_types/unique.py | 18 +++++ dropcalc/util.py | 4 + 16 files changed, 544 insertions(+) create mode 100644 dropcalc/.gitignore create mode 100644 dropcalc/LICENSE create mode 100644 dropcalc/Pipfile create mode 100644 dropcalc/Pipfile.lock create mode 100644 dropcalc/README.md create mode 100755 dropcalc/create_db.py create mode 100644 dropcalc/dropcalc-notes create mode 100644 dropcalc/mxl_types/__init__.py create mode 100644 dropcalc/mxl_types/base.py create mode 100644 dropcalc/mxl_types/dao.py create mode 100644 dropcalc/mxl_types/item_ratio.py create mode 100644 dropcalc/mxl_types/item_type.py create mode 100644 dropcalc/mxl_types/set.py create mode 100644 dropcalc/mxl_types/treasure_class.py create mode 100644 dropcalc/mxl_types/unique.py create mode 100644 dropcalc/util.py diff --git a/dropcalc/.gitignore b/dropcalc/.gitignore new file mode 100644 index 0000000..cfff1c9 --- /dev/null +++ b/dropcalc/.gitignore @@ -0,0 +1,6 @@ +# Intellij IDEA +*.iml +.idea/ + +# Python +__pycache__ diff --git a/dropcalc/LICENSE b/dropcalc/LICENSE new file mode 100644 index 0000000..eca96fd --- /dev/null +++ b/dropcalc/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2023 Starfall + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/dropcalc/Pipfile b/dropcalc/Pipfile new file mode 100644 index 0000000..99eb835 --- /dev/null +++ b/dropcalc/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +sqlalchemy = "*" + +[dev-packages] + +[requires] +python_version = "3.11" diff --git a/dropcalc/Pipfile.lock b/dropcalc/Pipfile.lock new file mode 100644 index 0000000..26425ac --- /dev/null +++ b/dropcalc/Pipfile.lock @@ -0,0 +1,147 @@ +{ + "_meta": { + "hash": { + "sha256": "47bb17e54bf2d6f4da261a2bd6e74be1c1ec26a8e00cb40956889302e10045cd" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "greenlet": { + "hashes": [ + "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174", + "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd", + "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa", + "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a", + "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec", + "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565", + "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d", + "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c", + "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234", + "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d", + "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546", + "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2", + "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74", + "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de", + "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd", + "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9", + "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3", + "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846", + "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2", + "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353", + "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8", + "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166", + "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206", + "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b", + "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d", + "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe", + "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997", + "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445", + "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0", + "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96", + "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884", + "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6", + "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1", + "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619", + "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94", + "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4", + "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1", + "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63", + "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd", + "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a", + "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376", + "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57", + "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16", + "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e", + "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc", + "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a", + "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c", + "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5", + "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a", + "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72", + "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9", + "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9", + "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e", + "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8", + "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65", + "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064", + "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36" + ], + "markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", + "version": "==3.0.1" + }, + "sqlalchemy": { + "hashes": [ + "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3", + "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884", + "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74", + "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d", + "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc", + "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca", + "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d", + "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf", + "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846", + "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306", + "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221", + "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5", + "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89", + "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55", + "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72", + "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea", + "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8", + "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577", + "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df", + "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4", + "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d", + "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34", + "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4", + "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24", + "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6", + "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965", + "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35", + "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b", + "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab", + "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22", + "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4", + "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204", + "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855", + "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d", + "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab", + "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69", + "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693", + "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e", + "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8", + "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0", + "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45", + "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab", + "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1", + "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d", + "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda", + "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b", + "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18", + "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac", + "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60" + ], + "index": "pypi", + "version": "==2.0.23" + }, + "typing-extensions": { + "hashes": [ + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + ], + "markers": "python_version >= '3.8'", + "version": "==4.8.0" + } + }, + "develop": {} +} diff --git a/dropcalc/README.md b/dropcalc/README.md new file mode 100644 index 0000000..265581d --- /dev/null +++ b/dropcalc/README.md @@ -0,0 +1,20 @@ +# Future home of Median XL Dropcalc + +and maybe a proper MXLDB eventually, too + +all it does right now is read some of the txts and dump them into an in-memory SQLite database, then do nothing with them + +## Dependencies + +Install python3, pip, and pipenv; I leave it up to the reader to decide how to do it because for me it's `paru -S python python-pip python-pipenv`. + +Run `pipenv shell` to create the dev environment which will install everything from the Pipfile (just sqlalchemy for now). + +If you don't like pipenv you still need python3 and pip, then just `pip install --user sqlalchemy`. + +## Usage +``` +create_db.py /path/to/txts +``` + +If you just give it the MOD repo or your d2 install folder (if you have the txts in it) that'll work too. diff --git a/dropcalc/create_db.py b/dropcalc/create_db.py new file mode 100755 index 0000000..be60fb6 --- /dev/null +++ b/dropcalc/create_db.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import csv +import mxl_types +from argparse import ArgumentParser +from pathlib import Path +from sqlalchemy import create_engine, text +from sqlalchemy.orm import Session + + +def generate_tcs(): + # TODO + # grab weapons, armor, misc? with spawnable=1 *and* who have itemtype with TreasureClass=1 (any itemtype? just direct?) + # qlvl 1-3 in weap3, 4-6 in weap6, 7-9 in weap9 ... 91-93 in weap93 + # armo3 ... armo99 + # vanilla has bow3 ... bow87 as well, unknown but probably based on the itemtype having Shoots and Quiver + # don't know if misc TCs are generated, neither median nor vanilla actually uses them if they are + pass + + +def create_db(folder): + engine = create_engine("sqlite+pysqlite:///:memory:", echo=False) + mxl_types.BaseDAO.metadata.create_all(engine) + + try1 = (Path(folder) / "Data" / "global" / "excel") + if try1.exists(): + data_folder = try1 + else: + data_folder = folder + + types = [ + mxl_types.ItemRatio, + mxl_types.ItemType, + mxl_types.Weapon, + mxl_types.Armor, + mxl_types.Misc, + mxl_types.UniqueItem, + mxl_types.SetItem, + mxl_types.TreasureClass, + ] + for mytype in types: + file = data_folder / mytype.filename + with file.open(encoding='windows-1252') as csvfile, Session(engine) as session: + reader = csv.DictReader(csvfile, dialect='excel-tab') + for csv_row in reader: + db_row = mytype(csv_row) + if not db_row.is_valid(): + continue + session.add(db_row) + session.commit() + count = session.scalar(text(f'select count(*) from {mytype.__tablename__}')) + print(f'Counted {count} {mytype.__tablename__}') + + +def handle_args(): + parser = ArgumentParser() + parser.add_argument('folder', help='Location of Median XL .txt files') + return parser.parse_args() + + +def main(): + args = handle_args() + create_db(args.folder) + + +if __name__ == '__main__': + import sys + + sys.exit(main()) diff --git a/dropcalc/dropcalc-notes b/dropcalc/dropcalc-notes new file mode 100644 index 0000000..b334e43 --- /dev/null +++ b/dropcalc/dropcalc-notes @@ -0,0 +1,61 @@ +TreasureClassEx.txt +tab-separated +Picks +Unique, Set, Rare, Magic +NoDrop +Item{1-10} : can be a TC name, Weapons/Armor/Misc/Gems code, UniqueItems index, maybe even SetItems index +Prob{1-10} + +group/level can upgrade tc but that's skippable for now until we need to deal with monsters + + + +for generic TCs: +read Weapons.txt, Armor.txt code, rarity +use name for now, deal with namestr & string tbls later + + + +NoDrop adjustment: +fundamentally it just retries once per (close, partied) player until an item is hit. +far and/or unpartied players count half + + +item quality: +ItemRatio.txt +Unique, UniqueDivisor, UniqueMin... etc + +chance = 128 * (Unique - (ilvl-qlvl)/Divisor) +eMF = (MF * factor) / (MF + Factor) like basically all diminishing returns +chance = chance * eMF +cap to minChance +chance = chance - (chance * tcUnique / 1024) + +roll 0 to chance-1, if less than 128 then success +which means lower "chance" is better + +unique is picked with rarity / total rarity of uniques for base that have the right ilvl as expected + +game rolls unique - set - rare - magic - superior - normal in order + + + +affixes the fuckening: +rares have 2+d4 affixes, 50/50 for each to be prefix or suffix until 3 +crafts have 4 affixes (or 0.4/0.2/0.2/0.2 for 1-4 at ilvl 1), otherwise the same +generate in order, exclude by group + +alvl of item is: +tmplvl = min(ilvl, 99) +tmplvl = max(qlvl, tmplvl) +if magic_level: + alvl = tmplvl + magic_level +else if tmplvl < (99 - qlvl/2): + alvl = tmplvl - qlvl/2 +else: + alvl = 2 * tmplvl - 99 +return min(alvl, 99) + +should likely monte carlo simulate crafts rather than try and calculate them + +need to fuck around with Itemtypes, MagicPrefix, MagicSuffix to get all the possible affixes diff --git a/dropcalc/mxl_types/__init__.py b/dropcalc/mxl_types/__init__.py new file mode 100644 index 0000000..09b00bc --- /dev/null +++ b/dropcalc/mxl_types/__init__.py @@ -0,0 +1,15 @@ +__all__ = ["ItemRatio", "ItemType", "BaseItem", "Weapon", "Armor", "Misc", "UniqueItem", "SetItem", "TreasureClass"] + +from .dao import BaseDAO + +from .item_ratio import ItemRatio +from .item_type import ItemType +from .base import BaseItem, Weapon, Armor, Misc +from .unique import UniqueItem +from .set import SetItem +from .treasure_class import TreasureClass + +# TODO +# MonStats grab Id, hcIdx, enabled, TreasureClass1(H), maybe remaining TC2/3/4(H) +# SuperUniques Superunique, TC(H) +# Levels MonLvl3 and nmon1-nmon20 diff --git a/dropcalc/mxl_types/base.py b/dropcalc/mxl_types/base.py new file mode 100644 index 0000000..cd7e8be --- /dev/null +++ b/dropcalc/mxl_types/base.py @@ -0,0 +1,34 @@ +from sqlalchemy import ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column + +from .dao import BaseDAO + + +class BaseItem(BaseDAO): + __tablename__ = "base" + + code: Mapped[str] = mapped_column(String(4), primary_key=True) + name: Mapped[str] # internal name only, game actually looks up `code` in strings + type = mapped_column(ForeignKey("item_type.Code")) # can have two item_type... and all their parents + type2 = mapped_column(ForeignKey("item_type.Code")) # should really figure out a better way to store them + rarity: Mapped[int] + level: Mapped[int] # variously referred to as ilvl or qlvl + spawnable: Mapped[int] # items with spawnable=0 *can* be dropped directly from TCs, but shouldn't randomly drop + category: Mapped[str] + + __mapper_args__ = {'polymorphic_on': 'category'} + + +class Weapon(BaseItem): + filename = "Weapons.txt" + __mapper_args__ = {'polymorphic_identity': 'weap'} + + +class Armor(BaseItem): + filename = "Armor.txt" + __mapper_args__ = {'polymorphic_identity': 'armo'} + + +class Misc(BaseItem): + filename = "Misc.txt" + __mapper_args__ = {'polymorphic_identity': 'misc'} diff --git a/dropcalc/mxl_types/dao.py b/dropcalc/mxl_types/dao.py new file mode 100644 index 0000000..db459e7 --- /dev/null +++ b/dropcalc/mxl_types/dao.py @@ -0,0 +1,13 @@ +from sqlalchemy.orm import DeclarativeBase + + +class BaseDAO(DeclarativeBase): + __csv_to_db__ = dict() + + def is_valid(self): + return True + + def __init__(self, iterable=(), **kwargs): + self.__dict__.update(iterable, **kwargs) + for this, that in self.__csv_to_db__.items(): + self.__dict__[this] = self.__dict__[that] diff --git a/dropcalc/mxl_types/item_ratio.py b/dropcalc/mxl_types/item_ratio.py new file mode 100644 index 0000000..7a783c3 --- /dev/null +++ b/dropcalc/mxl_types/item_ratio.py @@ -0,0 +1,39 @@ +from sqlalchemy.orm import Mapped, mapped_column + +from .dao import BaseDAO + + +class ItemRatio(BaseDAO): + filename = "ItemRatio_mp.txt" + __csv_to_db__ = {"Class_Specific": "Class Specific"} + __tablename__ = "item_ratio" + + id: Mapped[int] = mapped_column(primary_key=True) + + # D2Fileguide goes into more detail about how these are used + # the gist is 1 in (Quality - (mlvl-ilvl) / Divisor) * 128 of rolling each of these + # in the order unique, set, rare, magic, hiq, normal (and falling back on loq) + # further modified by MF after diminishing returns value (250 uni, 500 set, 600 rar) - which I believe is hardcoded + # and then the TC values + Unique: Mapped[int] + UniqueDivisor: Mapped[int] + UniqueMin: Mapped[int] + Rare: Mapped[int] + RareDivisor: Mapped[int] + RareMin: Mapped[int] + Set: Mapped[int] + SetDivisor: Mapped[int] + SetMin: Mapped[int] + Magic: Mapped[int] + MagicDivisor: Mapped[int] + MagicMin: Mapped[int] + HiQuality: Mapped[int] + HiQualityDivisor: Mapped[int] + Normal: Mapped[int] + NormalDivisor: Mapped[int] + + Uber: Mapped[int] # exceptional/elite as decided by normcode, ubercode, ultracode in Weapons.txt / Armor.txt + Class_Specific: Mapped[int] # class specifics as decided by Class in Itemtypes.txt + + def is_valid(self): + return self.Version == "1" diff --git a/dropcalc/mxl_types/item_type.py b/dropcalc/mxl_types/item_type.py new file mode 100644 index 0000000..8dd9b37 --- /dev/null +++ b/dropcalc/mxl_types/item_type.py @@ -0,0 +1,32 @@ +from typing import Optional + +from sqlalchemy import ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column + +from .dao import BaseDAO + + +class ItemType(BaseDAO): + filename = "ItemTypes.txt" + __tablename__ = "item_type" + + Id: Mapped[int] = mapped_column(primary_key=True) + Code: Mapped[str] = mapped_column(String(4)) # referred to by other tables + ItemType: Mapped[Optional[str]] # internal human readable name + + Equiv1 = mapped_column(ForeignKey("item_type.Code")) + Equiv2 = mapped_column(ForeignKey("item_type.Code")) + + # what does Rarity do here? fileguide description is unclear + + def is_valid(self): + return is_int(self.Id) and self.Code not in (None, '') + + +def is_int(obj): + try: + int(obj) + except ValueError: + return False + else: + return True diff --git a/dropcalc/mxl_types/set.py b/dropcalc/mxl_types/set.py new file mode 100644 index 0000000..498cdcf --- /dev/null +++ b/dropcalc/mxl_types/set.py @@ -0,0 +1,15 @@ +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column + +from . import BaseDAO + + +class SetItem(BaseDAO): + filename = "SetItems.txt" + __tablename__ = "set_item" + + id: Mapped[int] = mapped_column(primary_key=True) + index: Mapped[str] # string reference, often also human-readable + item = mapped_column(ForeignKey("base.code")) # note inconsistency with everything else... + rarity: Mapped[int] # drop frequency, relative to other sets on this base + lvl: Mapped[int] # minimum drop level diff --git a/dropcalc/mxl_types/treasure_class.py b/dropcalc/mxl_types/treasure_class.py new file mode 100644 index 0000000..d00da6f --- /dev/null +++ b/dropcalc/mxl_types/treasure_class.py @@ -0,0 +1,46 @@ +from typing import Optional + +from sqlalchemy.orm import Mapped, mapped_column + +from . import BaseDAO + + +class TreasureClass(BaseDAO): + filename = "TreasureClassEx.txt" + __csv_to_db__ = {"name": "Treasure Class"} + __tablename__ = "treasure_class" + + name: Mapped[str] = mapped_column(primary_key=True) + + group: Mapped[Optional[int]] + level: Mapped[Optional[int]] + + Picks: Mapped[int] + + Unique: Mapped[Optional[str]] + Set: Mapped[Optional[int]] + Rare: Mapped[Optional[int]] + Magic: Mapped[Optional[int]] + + NoDrop: Mapped[Optional[int]] + + Item1: Mapped[str] + Prob1: Mapped[int] + Item2: Mapped[Optional[str]] + Prob2: Mapped[Optional[int]] + Item3: Mapped[Optional[str]] + Prob3: Mapped[Optional[int]] + Item4: Mapped[Optional[str]] + Prob4: Mapped[Optional[int]] + Item5: Mapped[Optional[str]] + Prob5: Mapped[Optional[int]] + Item6: Mapped[Optional[str]] + Prob6: Mapped[Optional[int]] + Item7: Mapped[Optional[str]] + Prob7: Mapped[Optional[int]] + Item8: Mapped[Optional[str]] + Prob8: Mapped[Optional[int]] + Item9: Mapped[Optional[str]] + Prob9: Mapped[Optional[int]] + Item10: Mapped[Optional[str]] + Prob10: Mapped[Optional[int]] diff --git a/dropcalc/mxl_types/unique.py b/dropcalc/mxl_types/unique.py new file mode 100644 index 0000000..49c4e39 --- /dev/null +++ b/dropcalc/mxl_types/unique.py @@ -0,0 +1,18 @@ +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column + +from . import BaseDAO + + +class UniqueItem(BaseDAO): + filename = "UniqueItems.txt" + __tablename__ = "unique_item" + + id: Mapped[int] = mapped_column(primary_key=True) + index: Mapped[str] # string reference, often also human-readable + code = mapped_column(ForeignKey("base.code")) + rarity: Mapped[int] # drop frequency, relative to other uniques on this base + lvl: Mapped[int] # minimum drop level + + def is_valid(self): + return self.enabled == "1" # TODO: can we create fixed drop but not randomly available uniques by setting this to 0? diff --git a/dropcalc/util.py b/dropcalc/util.py new file mode 100644 index 0000000..923765b --- /dev/null +++ b/dropcalc/util.py @@ -0,0 +1,4 @@ +def is_int(x): + try: int(x) + except ValueError: return False + else: return True -- cgit