tibiawikisql 6.1.0__tar.gz → 6.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/CHANGELOG.rst +16 -0
- {tibiawikisql-6.1.0/tibiawikisql.egg-info → tibiawikisql-6.2.1}/PKG-INFO +1 -1
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/requirements.txt +2 -1
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/__init__.py +2 -2
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/__main__.py +159 -27
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/creature.py +4 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/imbuement.py +4 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/item.py +13 -10
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/npc.py +8 -4
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/schema.py +3 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/utils.py +21 -6
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1/tibiawikisql.egg-info}/PKG-INFO +1 -1
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/requires.txt +1 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/LICENSE +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/MANIFEST.in +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/README.md +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/setup.cfg +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/setup.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/__init__.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/test_models_utils.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/tests_models.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/tests_utils.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/tests_wikiapi.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/api.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/database.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/__init__.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/abc.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/achievement.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/charm.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/house.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/mount.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/outfit.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/quest.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/spell.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/update.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/world.py +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/SOURCES.txt +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/dependency_links.txt +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/entry_points.txt +0 -0
- {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
.. 6.2.1
|
|
6
|
+
|
|
7
|
+
6.2.1 (2024-06-27)
|
|
8
|
+
==================
|
|
9
|
+
- Fix database generation failing due to invalid date format.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.. 6.2.0
|
|
13
|
+
|
|
14
|
+
6.2.0 (2022-07-23)
|
|
15
|
+
==================
|
|
16
|
+
- Added `bosstiary_class` to creatures.
|
|
17
|
+
- NPC Offers are read from the ItemPrices data list.
|
|
18
|
+
- NPC Spell offers are read from the ItemPrices spell list.
|
|
19
|
+
- Performance improvements.
|
|
20
|
+
|
|
5
21
|
.. 6.1.0
|
|
6
22
|
|
|
7
23
|
6.1.0 (2022-01-04)
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"""API that reads and parses information from `TibiaWiki <https://tibiawiki.fandom.com>`_."""
|
|
16
16
|
|
|
17
17
|
__author__ = "Allan Galarza"
|
|
18
|
-
__copyright__ = "Copyright
|
|
18
|
+
__copyright__ = "Copyright 2024 Allan Galarza"
|
|
19
19
|
|
|
20
20
|
__license__ = "Apache 2.0"
|
|
21
|
-
__version__ = "6.1
|
|
21
|
+
__version__ = "6.2.1"
|
|
22
22
|
|
|
23
23
|
from tibiawikisql import models
|
|
24
24
|
from tibiawikisql.api import Article, Image, WikiClient, WikiEntry
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
14
|
+
import collections
|
|
15
15
|
import datetime
|
|
16
16
|
import json
|
|
17
17
|
import os
|
|
18
18
|
import platform
|
|
19
|
+
import re
|
|
19
20
|
import sqlite3
|
|
20
21
|
import time
|
|
21
22
|
from typing import Optional, Type
|
|
@@ -24,6 +25,7 @@ import click
|
|
|
24
25
|
import colorama
|
|
25
26
|
import requests
|
|
26
27
|
from colorama import Fore, Style
|
|
28
|
+
from lupa import LuaRuntime
|
|
27
29
|
|
|
28
30
|
from tibiawikisql import Image, WikiClient, __version__, models, schema
|
|
29
31
|
from tibiawikisql.models import abc
|
|
@@ -34,6 +36,8 @@ DATABASE_FILE = "tibiawiki.db"
|
|
|
34
36
|
|
|
35
37
|
colorama.init()
|
|
36
38
|
|
|
39
|
+
lua = LuaRuntime()
|
|
40
|
+
|
|
37
41
|
|
|
38
42
|
def progress_bar(iterable, label, length, **kwargs):
|
|
39
43
|
return click.progressbar(iterable=iterable, length=length, label=label, fill_char="█", empty_char="░", width=10,
|
|
@@ -53,23 +57,24 @@ class Category:
|
|
|
53
57
|
|
|
54
58
|
Class for internal use only, for easier autocompletion and maintenance."""
|
|
55
59
|
def __init__(self, name: Optional[str], model: Type[abc.Row] = None, *, no_images=False, extension=".gif",
|
|
56
|
-
include_deprecated=False, no_title=False):
|
|
60
|
+
include_deprecated=False, no_title=False, generate_map=False):
|
|
57
61
|
self.name = name
|
|
58
62
|
self.model = model
|
|
59
63
|
self.no_images = no_images
|
|
60
64
|
self.extension = extension
|
|
61
65
|
self.include_deprecated = include_deprecated
|
|
62
66
|
self.no_title = no_title
|
|
67
|
+
self.generate_map = generate_map
|
|
63
68
|
|
|
64
69
|
|
|
65
70
|
categories = {
|
|
66
71
|
"achievements": Category("Achievements", models.Achievement, no_images=True),
|
|
67
|
-
"spells": Category("Spells", models.Spell),
|
|
68
|
-
"items": Category("Objects", models.Item),
|
|
69
|
-
"creatures": Category("Creatures", models.Creature),
|
|
72
|
+
"spells": Category("Spells", models.Spell, generate_map=True),
|
|
73
|
+
"items": Category("Objects", models.Item, generate_map=True),
|
|
74
|
+
"creatures": Category("Creatures", models.Creature, generate_map=True),
|
|
70
75
|
"books": Category("Book Texts", models.Book, no_images=True),
|
|
71
76
|
"keys": Category("Keys", models.Key, no_images=True),
|
|
72
|
-
"npcs": Category("NPCs", models.Npc),
|
|
77
|
+
"npcs": Category("NPCs", models.Npc, generate_map=True),
|
|
73
78
|
"imbuements": Category("Imbuements", models.Imbuement, extension=".png"),
|
|
74
79
|
"quests": Category("Quest Overview Pages", models.Quest, no_images=True),
|
|
75
80
|
"house": Category("Player-Ownable Buildings", models.House, no_images=True),
|
|
@@ -111,6 +116,9 @@ def generate(skip_images, db_name, skip_deprecated):
|
|
|
111
116
|
if not issubclass(model, abc.Parseable):
|
|
112
117
|
continue
|
|
113
118
|
titles = [a.title for a in data_store[key]]
|
|
119
|
+
|
|
120
|
+
if value.generate_map:
|
|
121
|
+
data_store[f"{key}_map"] = {}
|
|
114
122
|
unparsed = []
|
|
115
123
|
exec_time = time.perf_counter()
|
|
116
124
|
generator = WikiClient.get_articles(titles)
|
|
@@ -120,6 +128,8 @@ def generate(skip_images, db_name, skip_deprecated):
|
|
|
120
128
|
entry = model.from_article(article)
|
|
121
129
|
if entry is not None:
|
|
122
130
|
entry.insert(conn)
|
|
131
|
+
if value.generate_map:
|
|
132
|
+
data_store[f"{key}_map"][entry.title.lower()] = entry.article_id
|
|
123
133
|
else:
|
|
124
134
|
unparsed.append(article.title)
|
|
125
135
|
if unparsed:
|
|
@@ -131,7 +141,9 @@ def generate(skip_images, db_name, skip_deprecated):
|
|
|
131
141
|
for position in rashid_positions:
|
|
132
142
|
position.insert(conn)
|
|
133
143
|
|
|
134
|
-
|
|
144
|
+
generate_item_offers(conn, data_store)
|
|
145
|
+
generate_spell_offers(conn, data_store)
|
|
146
|
+
generate_loot_statistics(conn, data_store)
|
|
135
147
|
|
|
136
148
|
if not skip_images:
|
|
137
149
|
with conn:
|
|
@@ -154,45 +166,165 @@ def generate(skip_images, db_name, skip_deprecated):
|
|
|
154
166
|
click.echo(f"Command finished in {dt:.2f} seconds.")
|
|
155
167
|
|
|
156
168
|
|
|
157
|
-
|
|
169
|
+
link_pattern = re.compile(r"(?:(?P<price>\d+))?\s?\[\[([^\]|]+)")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def generate_spell_offers(conn: sqlite3.Connection, data_store):
|
|
173
|
+
if "npcs_map" not in data_store or "spells_map" not in data_store:
|
|
174
|
+
return
|
|
175
|
+
start_time = time.perf_counter()
|
|
176
|
+
article = WikiClient.get_article("Module:ItemPrices/spelldata")
|
|
177
|
+
data = lua.execute(article.content)
|
|
178
|
+
|
|
179
|
+
spell_offers = []
|
|
180
|
+
not_found_store = collections.defaultdict(set)
|
|
181
|
+
spells = list(data.items())
|
|
182
|
+
with progress_bar(spells, "Fetching spell offers", len(spells)) as bar:
|
|
183
|
+
for name, table in bar:
|
|
184
|
+
spell_id = data_store["spells_map"].get(name.lower())
|
|
185
|
+
if spell_id is None:
|
|
186
|
+
not_found_store["spell"].add(name)
|
|
187
|
+
continue
|
|
188
|
+
spell_vocations = list((table["vocation"].values()))
|
|
189
|
+
for npc, vocation in table["sellers"].items():
|
|
190
|
+
npc_id = data_store["npcs_map"].get(npc.lower())
|
|
191
|
+
if npc_id is None:
|
|
192
|
+
not_found_store["item"].add(npc)
|
|
193
|
+
continue
|
|
194
|
+
if isinstance(vocation, bool):
|
|
195
|
+
npc_vocations = spell_vocations
|
|
196
|
+
elif isinstance(vocation, str):
|
|
197
|
+
npc_vocations = [vocation]
|
|
198
|
+
else:
|
|
199
|
+
npc_vocations = list(vocation.values())
|
|
200
|
+
spell_offers.append((
|
|
201
|
+
npc_id,
|
|
202
|
+
spell_id,
|
|
203
|
+
"Knight" in npc_vocations,
|
|
204
|
+
"Sorcerer" in npc_vocations,
|
|
205
|
+
"Druid" in npc_vocations,
|
|
206
|
+
"Paladin" in npc_vocations,
|
|
207
|
+
))
|
|
208
|
+
with conn:
|
|
209
|
+
conn.execute("DELETE FROM npc_spell")
|
|
210
|
+
conn.executemany("INSERT INTO npc_spell(npc_id, spell_id, knight, sorcerer, paladin, druid) VALUES(?, ?, ?, ?, ?, ?)", spell_offers)
|
|
211
|
+
if not_found_store["spell"]:
|
|
212
|
+
unknonw_spells = not_found_store["spell"]
|
|
213
|
+
click.echo(f"{Fore.RED}Could not parse offers for {len(unknonw_spells):,} spell.{Style.RESET_ALL}")
|
|
214
|
+
click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknonw_spells)}{Style.RESET_ALL}")
|
|
215
|
+
if not_found_store["npc"]:
|
|
216
|
+
unknown_npcs = not_found_store["npc"]
|
|
217
|
+
click.echo(f"{Fore.RED}Could not parse offers of {len(unknown_npcs):,} npcs.{Style.RESET_ALL}")
|
|
218
|
+
click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknown_npcs)}{Style.RESET_ALL}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def generate_item_offers(conn: sqlite3.Connection, data_store):
|
|
222
|
+
if "npcs_map" not in data_store or "items_map" not in data_store:
|
|
223
|
+
return
|
|
224
|
+
start_time = time.perf_counter()
|
|
225
|
+
article = WikiClient.get_article("Module:ItemPrices/data")
|
|
226
|
+
data = lua.execute(article.content)
|
|
227
|
+
|
|
228
|
+
sell_offers = []
|
|
229
|
+
buy_offers = []
|
|
230
|
+
not_found_store = collections.defaultdict(set)
|
|
231
|
+
npc_offers = list(data.items())
|
|
232
|
+
with progress_bar(npc_offers, "Fetching NPC offers", len(npc_offers)) as bar:
|
|
233
|
+
for name, table in bar:
|
|
234
|
+
npc_id = data_store["npcs_map"].get(name.lower())
|
|
235
|
+
if npc_id is None:
|
|
236
|
+
not_found_store["npc"].add(name)
|
|
237
|
+
continue
|
|
238
|
+
if "sells" in table:
|
|
239
|
+
process_offer_list(npc_id, table["sells"], sell_offers, data_store, not_found_store)
|
|
240
|
+
if "buys" in table:
|
|
241
|
+
process_offer_list(npc_id, table["buys"], buy_offers, data_store, not_found_store)
|
|
242
|
+
with conn:
|
|
243
|
+
conn.execute("DELETE FROM npc_offer_sell")
|
|
244
|
+
conn.execute("DELETE FROM npc_offer_buy")
|
|
245
|
+
conn.executemany("INSERT INTO npc_offer_sell(npc_id, value, item_id, currency_id) VALUES(?, ?, ?, ?)", sell_offers)
|
|
246
|
+
conn.executemany("INSERT INTO npc_offer_buy(npc_id, value, item_id, currency_id) VALUES(?, ?, ?, ?)", buy_offers)
|
|
247
|
+
dt = (time.perf_counter() - start_time)
|
|
248
|
+
total_offers = len(sell_offers) + len(buy_offers)
|
|
249
|
+
if not_found_store["npc"]:
|
|
250
|
+
unknonw_npcs = not_found_store["npc"]
|
|
251
|
+
click.echo(f"{Fore.RED}Could not parse offers for {len(unknonw_npcs):,} npcs.{Style.RESET_ALL}")
|
|
252
|
+
click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknonw_npcs)}{Style.RESET_ALL}")
|
|
253
|
+
if not_found_store["item"]:
|
|
254
|
+
unknonw_items = not_found_store["item"]
|
|
255
|
+
click.echo(f"{Fore.RED}Could not parse offers for {len(unknonw_items):,} items.{Style.RESET_ALL}")
|
|
256
|
+
click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknonw_items)}{Style.RESET_ALL}")
|
|
257
|
+
click.echo(f"{Fore.GREEN}\tSaved {total_offers:,} NPC offers in {dt:.2f} seconds.")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def process_offer_list(npc_id, array, list_store, data_store, not_found):
|
|
261
|
+
for lua_item in array.values():
|
|
262
|
+
data = dict(lua_item.items())
|
|
263
|
+
price = data["price"]
|
|
264
|
+
currency = data.get("currency", "gold coin")
|
|
265
|
+
if not isinstance(price, int):
|
|
266
|
+
m = link_pattern.search(price)
|
|
267
|
+
price = int(m.group("price") or 1)
|
|
268
|
+
currency = m.group(2)
|
|
269
|
+
item_name = data["item"]
|
|
270
|
+
currency_name = currency
|
|
271
|
+
item_id = data_store["items_map"].get(item_name.lower())
|
|
272
|
+
currency_id = data_store["items_map"].get(currency_name.lower())
|
|
273
|
+
if currency_id is None:
|
|
274
|
+
not_found["item"].add(currency_name)
|
|
275
|
+
continue
|
|
276
|
+
if item_id is None:
|
|
277
|
+
not_found["item"].add(item_name)
|
|
278
|
+
continue
|
|
279
|
+
list_store.append((
|
|
280
|
+
npc_id,
|
|
281
|
+
price,
|
|
282
|
+
item_id,
|
|
283
|
+
currency_id
|
|
284
|
+
))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def generate_loot_statistics(conn: sqlite3.Connection, data_store):
|
|
158
288
|
c = conn.cursor()
|
|
159
289
|
try:
|
|
160
290
|
results = conn.execute("SELECT title FROM creature")
|
|
161
291
|
titles = [f"Loot Statistics:{t[0]}" for t in results]
|
|
162
292
|
start_time = time.perf_counter()
|
|
293
|
+
unknown_items = set()
|
|
163
294
|
with progress_bar(WikiClient.get_articles(titles), "Fetching loot statistics", len(titles),
|
|
164
295
|
item_show_func=article_show) as bar:
|
|
165
296
|
for article in bar:
|
|
166
297
|
if article is None:
|
|
167
298
|
continue
|
|
168
299
|
creature_title = article.title.replace("Loot Statistics:", "")
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if result is None:
|
|
300
|
+
creature_id = data_store["creatures_map"].get(creature_title.lower())
|
|
301
|
+
if creature_id is None:
|
|
172
302
|
# This could happen if a creature's article was deleted but its Loot Statistics weren't
|
|
173
303
|
continue
|
|
174
|
-
|
|
175
|
-
# Most loot statistics contain stats for older versions too, we onl care about the latest version.
|
|
304
|
+
# Most loot statistics contain stats for older versions too, we only care about the latest version.
|
|
176
305
|
kills, loot_stats = parse_loot_statistics(article.content)
|
|
177
306
|
loot_items = []
|
|
178
307
|
for entry in loot_stats:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
308
|
+
if entry:
|
|
309
|
+
item = entry["item"]
|
|
310
|
+
times = entry["times"]
|
|
311
|
+
amount = entry.get("amount", 1)
|
|
312
|
+
item_id = data_store["items_map"].get(item.lower())
|
|
313
|
+
if item_id is None:
|
|
314
|
+
unknown_items.add(item)
|
|
315
|
+
continue
|
|
316
|
+
percentage = min(int(times) / kills * 100, 100)
|
|
317
|
+
_min, _max = parse_min_max(amount)
|
|
318
|
+
loot_items.append((creature_id, item_id, percentage, _min, _max))
|
|
319
|
+
# We delete any duplicate record that was added from the creature's article's loot if it exists
|
|
320
|
+
c.execute("DELETE FROM creature_drop WHERE creature_id = ? AND item_id = ?",
|
|
321
|
+
(creature_id, item_id))
|
|
193
322
|
c.executemany("INSERT INTO creature_drop(creature_id, item_id, chance, min, max) VALUES(?,?,?,?,?)",
|
|
194
323
|
loot_items)
|
|
195
324
|
dt = (time.perf_counter() - start_time)
|
|
325
|
+
if unknown_items:
|
|
326
|
+
click.echo(f"{Fore.RED}Could not find {len(unknown_items):,} items.{Style.RESET_ALL}")
|
|
327
|
+
click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknown_items)}{Style.RESET_ALL}")
|
|
196
328
|
click.echo(f"{Fore.GREEN}\tParsed loot statistics in {dt:.2f} seconds.{Style.RESET_ALL}")
|
|
197
329
|
finally:
|
|
198
330
|
conn.commit()
|
|
@@ -261,6 +261,8 @@ class Creature(abc.Row, abc.Parseable, table=schema.Creature):
|
|
|
261
261
|
The creature's bestiary level, from 'Trivial' to 'Hard'
|
|
262
262
|
bestiary_occurrence: :class:`str`
|
|
263
263
|
The creature's bestiary occurrence, from 'Common' to 'Very Rare'.
|
|
264
|
+
bosstiary_class: :class:`str`
|
|
265
|
+
The creature's bosstiary class, if applicable.
|
|
264
266
|
hitpoints: :class:`int`
|
|
265
267
|
The creature's hitpoints, may be `None` if unknown.
|
|
266
268
|
experience: :class:`int`
|
|
@@ -336,6 +338,7 @@ class Creature(abc.Row, abc.Parseable, table=schema.Creature):
|
|
|
336
338
|
"creatureclass": ("creature_class", str.strip),
|
|
337
339
|
"bestiaryclass": ("bestiary_class", str.strip),
|
|
338
340
|
"bestiarylevel": ("bestiary_level", str.strip),
|
|
341
|
+
"bosstiaryclass": ("bosstiary_class", str.strip),
|
|
339
342
|
"occurrence": ("bestiary_occurrence", str.strip),
|
|
340
343
|
"primarytype": ("creature_type", str.strip),
|
|
341
344
|
"secondarytype": ("type_secondary", str.strip),
|
|
@@ -384,6 +387,7 @@ class Creature(abc.Row, abc.Parseable, table=schema.Creature):
|
|
|
384
387
|
"bestiary_level",
|
|
385
388
|
"bestiary_class",
|
|
386
389
|
"bestiary_occurrence",
|
|
390
|
+
"bosstiary_class",
|
|
387
391
|
"hitpoints",
|
|
388
392
|
"experience",
|
|
389
393
|
"armor",
|
|
@@ -123,6 +123,8 @@ class Imbuement(abc.Row, abc.Parseable, table=schema.Imbuement):
|
|
|
123
123
|
The tier of the imbuement.
|
|
124
124
|
type: :class:`str`
|
|
125
125
|
The imbuement's type.
|
|
126
|
+
category: :class:`str`
|
|
127
|
+
The imbuement's category.
|
|
126
128
|
effect: :class:`str`
|
|
127
129
|
The effect given by the imbuement.
|
|
128
130
|
slots: :class:`str`
|
|
@@ -141,6 +143,7 @@ class Imbuement(abc.Row, abc.Parseable, table=schema.Imbuement):
|
|
|
141
143
|
"name": ("name", str.strip),
|
|
142
144
|
"prefix": ("tier", str.strip),
|
|
143
145
|
"type": ("type", str.strip),
|
|
146
|
+
"category": ("category", str.strip),
|
|
144
147
|
"effect": ("effect", parse_effect),
|
|
145
148
|
"implemented": ("version", str.strip),
|
|
146
149
|
"slots": ("slots", parse_slots),
|
|
@@ -155,6 +158,7 @@ class Imbuement(abc.Row, abc.Parseable, table=schema.Imbuement):
|
|
|
155
158
|
"name",
|
|
156
159
|
"tier",
|
|
157
160
|
"type",
|
|
161
|
+
"category",
|
|
158
162
|
"effect",
|
|
159
163
|
"slots",
|
|
160
164
|
"version",
|
|
@@ -282,15 +282,18 @@ class Item(abc.Row, abc.Parseable, table=schema.Item):
|
|
|
282
282
|
if attribute in item._raw_attributes and item._raw_attributes[attribute]:
|
|
283
283
|
item.attributes.append(ItemAttribute(item_id=item.article_id, name=name,
|
|
284
284
|
value=item._raw_attributes[attribute]))
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
285
|
+
try:
|
|
286
|
+
item._parse_attributes()
|
|
287
|
+
item._parse_resists()
|
|
288
|
+
vocations = item._raw_attributes.get('vocrequired')
|
|
289
|
+
if vocations and "none" not in vocations.lower():
|
|
290
|
+
vocation = vocations.replace('and', '+').replace(',', '+').replace(' ', '')
|
|
291
|
+
item.attributes.append(ItemAttribute(item_id=item.article_id, name="vocation", value=vocation))
|
|
292
|
+
item._parse_sounds()
|
|
293
|
+
item._parse_store_value()
|
|
294
|
+
return item
|
|
295
|
+
except Exception:
|
|
296
|
+
return None
|
|
294
297
|
|
|
295
298
|
def _parse_attributes(self):
|
|
296
299
|
if "attrib" not in self._raw_attributes:
|
|
@@ -600,7 +603,7 @@ class ItemAttribute(abc.Row, table=schema.ItemAttribute):
|
|
|
600
603
|
"unshootable": "unshootable",
|
|
601
604
|
"blocks_path": "blockspath",
|
|
602
605
|
"walkable": "walkable",
|
|
603
|
-
"
|
|
606
|
+
"tile_friction": "walkingspeed",
|
|
604
607
|
"map_color": "mapcolor",
|
|
605
608
|
"upgrade_classification": "upgradeclass"
|
|
606
609
|
}
|
|
@@ -164,6 +164,8 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
|
|
|
164
164
|
The jobs of the NPC.
|
|
165
165
|
location: :class:`str`
|
|
166
166
|
The location of the NPC.
|
|
167
|
+
subarea: :class:`str`
|
|
168
|
+
A finer location of the NPC.
|
|
167
169
|
city: :class:`str`
|
|
168
170
|
The nearest city to where the NPC is located.
|
|
169
171
|
x: :class:`int`
|
|
@@ -198,6 +200,7 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
|
|
|
198
200
|
"jobs",
|
|
199
201
|
"location",
|
|
200
202
|
"city",
|
|
203
|
+
"subarea",
|
|
201
204
|
"x",
|
|
202
205
|
"y",
|
|
203
206
|
"z",
|
|
@@ -219,6 +222,7 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
|
|
|
219
222
|
"location": ("location", clean_links),
|
|
220
223
|
"gender": ("gender", str.strip),
|
|
221
224
|
"city": ("city", str.strip),
|
|
225
|
+
"subarea": ("subarea", str.strip),
|
|
222
226
|
"posx": ("x", convert_tibiawiki_position),
|
|
223
227
|
"posy": ("y", convert_tibiawiki_position),
|
|
224
228
|
"posz": ("z", int),
|
|
@@ -359,10 +363,10 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
|
|
|
359
363
|
|
|
360
364
|
def insert(self, c):
|
|
361
365
|
super().insert(c)
|
|
362
|
-
for offer in getattr(self, "buy_offers", []):
|
|
363
|
-
|
|
364
|
-
for offer in getattr(self, "sell_offers", []):
|
|
365
|
-
|
|
366
|
+
# for offer in getattr(self, "buy_offers", []):
|
|
367
|
+
# offer.insert(c)
|
|
368
|
+
# for offer in getattr(self, "sell_offers", []):
|
|
369
|
+
# offer.insert(c)
|
|
366
370
|
for spell in getattr(self, "teaches", []):
|
|
367
371
|
spell.insert(c)
|
|
368
372
|
for destination in getattr(self, "destinations", []):
|
|
@@ -63,6 +63,7 @@ class Creature(Table):
|
|
|
63
63
|
bestiary_class = Column(Text, index=True)
|
|
64
64
|
bestiary_level = Column(Text, index=True)
|
|
65
65
|
bestiary_occurrence = Column(Text, index=True)
|
|
66
|
+
bosstiary_class = Column(Text, index=True)
|
|
66
67
|
runs_at = Column(Integer)
|
|
67
68
|
summon_cost = Column(Integer)
|
|
68
69
|
convince_cost = Column(Integer)
|
|
@@ -220,6 +221,7 @@ class Imbuement(Table):
|
|
|
220
221
|
name = Column(Text, unique=True, index=True)
|
|
221
222
|
tier = Column(Text, nullable=False)
|
|
222
223
|
type = Column(Text, nullable=False)
|
|
224
|
+
category = Column(Text, nullable=False)
|
|
223
225
|
effect = Column(Text)
|
|
224
226
|
slots = Column(Text)
|
|
225
227
|
version = Column(Text, index=True)
|
|
@@ -290,6 +292,7 @@ class Npc(Table):
|
|
|
290
292
|
name = Column(Text, no_case=True, index=True)
|
|
291
293
|
gender = Column(Text, index=True)
|
|
292
294
|
city = Column(Text, index=True)
|
|
295
|
+
subarea = Column(Text)
|
|
293
296
|
location = Column(Text)
|
|
294
297
|
version = Column(Text, index=True)
|
|
295
298
|
x = Column(Integer)
|
|
@@ -188,6 +188,7 @@ def parse_date(value):
|
|
|
188
188
|
|
|
189
189
|
- June 28, 2019
|
|
190
190
|
- Aug 21, 2014
|
|
191
|
+
- May 14, 2024 17:45
|
|
191
192
|
|
|
192
193
|
Parameters
|
|
193
194
|
----------
|
|
@@ -200,11 +201,22 @@ def parse_date(value):
|
|
|
200
201
|
The date represented by the string.
|
|
201
202
|
"""
|
|
202
203
|
value = value.strip()
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
date_formats = [
|
|
205
|
+
"%B %d, %Y",
|
|
206
|
+
"%b %d, %Y",
|
|
207
|
+
"%Y",
|
|
208
|
+
"%B %d, %Y %H:%M",
|
|
209
|
+
"%b %d, %Y %H:%M",
|
|
210
|
+
"%Y %H:%M",
|
|
211
|
+
]
|
|
212
|
+
for date_format in date_formats:
|
|
213
|
+
try:
|
|
214
|
+
dt = datetime.datetime.strptime(value, date_format)
|
|
215
|
+
return dt.date().isoformat()
|
|
216
|
+
except ValueError:
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
raise ValueError(f"Date format for value '{value}' not recognized")
|
|
208
220
|
|
|
209
221
|
|
|
210
222
|
def parse_float(value, default=0.0):
|
|
@@ -297,7 +309,10 @@ def _parse_loot_entry(entry):
|
|
|
297
309
|
key = subarg[0].strip()
|
|
298
310
|
value = subarg[1].strip()
|
|
299
311
|
entry[key] = value
|
|
300
|
-
|
|
312
|
+
if "item" in entry:
|
|
313
|
+
return entry
|
|
314
|
+
else:
|
|
315
|
+
return None
|
|
301
316
|
|
|
302
317
|
|
|
303
318
|
def parse_min_max(value):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|