Legend of the Invincibles
Moderator: Forum Moderators
Re: Legend of the Invincibles
This was happening to me as well. I think it became an issue once one of the characters on the autorecall list had died (if I remember correctly, in my case it was an unnamed wrath). In other words, I could alter my recall list until someone died. Once they died, going into the autorecall dialogues crashed the game.Zarkussell wrote:@dugi
There seems to be a bug in chapter 5 after you complete several scenarios with the configure recall list button where the game simply freezes until you close it and reopen it.
Re: Legend of the Invincibles
I'm not proficient with either lua or wml, but wouldn't another fresh-start solution to be to give everything an item number (or hash table plus an associated pre-sorted index) and starting everything with item quantity = 0. Then when making the menu selection list, only list those with positive quantity?matsjoyce wrote:OK, found some problems. I don't know how it sorted for me, but I've given it a good testing now (~70 items added), and it works great. I've attached a diff (right way round now )). If the inventory already contains items, it will probably do some sort of grouping, but won't sort the existing items, so its a sort of clean start requiring feature.
An issue I see with this proposal is that if additional items are added to the game code as development occurs, all of a sudden your items might morph if they appear in a slightly different spot in the list (i.e. if what was once item 2 is now item 3). I don't know if mapped key/values would be a way to work around this or if wml mapping/filtering functions are too slow.
Re: Legend of the Invincibles
@Pags
Do you have a save file where the autorecall issue can be replicated?
Some items are not just numbers. The crafted items have an additional attribute, the base type. That would not work with purely marking counts of each item. I was also considering an option to allow gems to slightly alter the properties of items, so I designed it to allow this possibility.
Do you have a save file where the autorecall issue can be replicated?
Some items are not just numbers. The crafted items have an additional attribute, the base type. That would not work with purely marking counts of each item. I was also considering an option to allow gems to slightly alter the properties of items, so I designed it to allow this possibility.
Re: Legend of the Invincibles
I'll have to check. I assume you'd want before-and-after save games (before = recall list works fine, after=crash whenever you access the menu). As it's a crash out of the game client (AFAIR) it could be platform dependent. In which case, all will be Ubuntu Linux (version 14?)Dugi wrote:@Pags
Do you have a save file where the autorecall issue can be replicated?
Good points. Speaking of which, at least in the Beautiful Child campaign, crafting a weapon results in an icon of a backpack/pouch, rather than grabbing a generic image of that weapon type. Sword of thorns (a random drop) graphic appears as just a black background when involved in combat. I think it had a sword image when displayed on the map pre-pickup. Do you need savegame files for that too?Some items are not just numbers. The crafted items have an additional attribute, the base type. That would not work with purely marking counts of each item. I was also considering an option to allow gems to slightly alter the properties of items, so I designed it to allow this possibility.
Re: Legend of the Invincibles
I believe it's not platform dependent. And I also have Ubuntu 14(.10.), so it does not matter anyway.As it's a crash out of the game client (AFAIR) it could be platform dependent. In which case, all will be Ubuntu Linux (version 14?)
This applies everywhere. I was lazy to add some special visuals for the case when it's on the ground, because the cases when you actually put it on the ground for any reason are rare.Speaking of which, at least in the Beautiful Child campaign, crafting a weapon results in an icon of a backpack/pouch, rather than grabbing a generic image of that weapon type.
I have fixed this already, just not uploaded yet.Sword of thorns (a random drop) graphic appears as just a black background when involved in combat. I think it had a sword image when displayed on the map pre-pickup. Do you need savegame files for that too?
Re: Legend of the Invincibles
The investigation of the save file of a leader not attacking even a completely vulnerable enemy has shown that it was an old save file that did not have the custom AI enabled. Therefore, it would be helpful to me if someone attached a save file where a leader does not attack an unprotected unit (he should not rush into a group of your units)?
Re: Legend of the Invincibles
@Pags
@Dugi
Also, did you see viewtopic.php?f=23&t=32370 as some of those may be useful, due to the amount of magic / magic-based attacks in LotI
Done! Version 0.3 of the script now generates a page containing abilities and weapon specials with their descriptions at https://wiki.wesnoth.org/LotI_Abilities. Latest code:Pags wrote: It would be helpful for you to provide links to help about the weapon special attributes. E.g. link to a page that shows what "focused" means (if that's possible and relatively trivial)
Code: Select all
#!/usr/bin/env python3
#
# Copyright 2016 Matthew Joyce
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import pathlib
import re
import collections
import itertools
import builtins
import time
import sys
import shlex
__version__ = "0.3"
wml_regexes = [("key", r"(\w+)\s*=\s*_?\s*\"([^\"]*)\""),
("key", r"(\w+)\s*=\s*([^\n]+)"),
("keys", r"([\w,]+)\s*=\s*([^\n]+)"),
("open", r"\[(\w+)\]"),
("close", r"\[/(\w+)\]"),
("macro_open", r"(\{[^{}]+)"),
("pre", r"#(define|ifdef|else|endif|enddef) ?([^\n]*)"),
("whitespace", r"(\s+)"),
("comment", r"#[^\n]*")]
wml_regexes = [(n, re.compile(r, re.DOTALL)) for n, r in wml_regexes]
levels = ["EASY", "MEDIUM", "HARD"]
sort_translations = {"weaponword": "craftable as any weapon",
"armourword": "craftable as any armour",
}
weapon_sorts = ["sword", "axe", "bow", "mace", "xbow", "spear", "dagger", "knife", "staff",
"weaponword", "polearm", "sling", "thunderstick", "claws", "essence", "exotic"]
damage_ranges = ["", "_melee", "_ranged"]
damage_types = ["blade", "impact", "pierce", "fire", "cold", "arcane"]
defence_types = [("forest", "in forests"),
("frozen", "on frozen places"),
("flat", "on flat terrains"),
("cave", "in caves"),
("fungus", "in mushroom groves"),
("village", "in villages"),
("castle", "in castles"),
("shallow_water", "in shallow waters"),
("reef", "on coastal reefs"),
("deep_water", "in deep water"),
("swamp_water", "in swamps"),
("hills", "on hills"),
("mountains", "on mountains"),
("sand", "on sands"),
("unwalkable", "above unwalkable places"),
("impassable", "inside impassable walls")]
movement_costs = [("forest", "through forests"),
("frozen", "on frozen lands"),
("flat", "on flat terrains"),
("cave", "through dark caves"),
("fungus", "through mushroom groves"),
("village", "through villages"),
("castle", "through castles"),
("shallow_water", "in shallow waters"),
("reef", "on coastal reefs"),
("deep_water", "in deep waters"),
("swamp_water", "through swampy places"),
("hills", "on hills"),
("mountains", "on mountains"),
("sand", "across sands"),
("unwalkable", "above unwalkable places"),
("impassable", "through impassable walls")]
special_translation = {"NOSFERATU_GORGE": "nosferatu's gorge",
"RAIJERS_SALOON": "Raijer's saloon"
}
ability_translation = {"BERSERK_LEADERSHIP": "radiating insanity",
"CHARGE_LEADERSHIP": "warlord's rule",
"POISON_LEADERSHIP": "radiation",
"FIRSTSTRIKE_LEADERSHIP": "zeal aura",
"BACKSTAB_LEADERSHIP": "murderous presence",
"MARKSMAN_LEADERSHIP": "cantor",
"DRAIN_LEADERSHIP": "aura of hunger",
"REGENERATES_LESSER": "regenerates slightly",
"PENETRATE_LEADERSHIP": "push",
"DARKENS_IMPROVED": "darkens badly",
"DARKENS_GREAT": "darkens severely",
"ILLUMINATES_IMPROVED": "improved illumination",
"ILLUMINATES_GREAT": "great illumination",
"LEADERSHIP_LEVEL_6": "leadership",
"WEAK_AMBUSH": "lesser ambush",
"IMMUNE_TO_SLOW": "resistant to slow",
"PENETRATE": "penetrates",
"TOXIC_AURA": "dark aura",
"FEEDING_EASY": "feeding",
}
header = """
This is an auto-generated wiki page listing {{}} currently avalible in the campaign "Legend of the Invincibles". {{}}
This was generated at {} using version {{}} of LotI and version {} of the generation script.
As this is auto-generated, DO NOT EDIT THIS PAGE.
Instead, make suggestions at https://forums.wesnoth.org/viewtopic.php?f=8&t=32384 and the script will be adjusted.
Other LotI-related wiki pages:
* https://wiki.wesnoth.org/LotI_Items – items, such as weapons and books
* https://wiki.wesnoth.org/LotI_Standard_Advancements – general advancements such as legacies and books
* https://wiki.wesnoth.org/LotI_Unit_Advancements – unit specific advancements
* https://wiki.wesnoth.org/LotI_Abilities – abilities and weapon specials
* https://wiki.wesnoth.org/DeadlyUnitsFromLotI
""".lstrip().format(time.ctime(), __version__)
WMLTag = collections.namedtuple("WMLTag", ("keys", "tags", "annotation", "macros"))
class WMLValue:
def __init__(self):
self.EASY = self.MEDIUM = self.HARD = ""
@property
def any(self):
return self.EASY or self.MEDIUM or self.HARD
@property
def all(self):
return self.EASY == self.MEDIUM == self.HARD
def iter(self):
yield "EASY", self.EASY
yield "MEDIUM", self.MEDIUM
yield "HARD", self.HARD
def __repr__(self):
return "WMLValue({!r}, {!r}, {!r})".format(self.EASY, self.MEDIUM, self.HARD)
def tokenize(str):
str = re.sub(r"\"\s*\+\s*\{([^}]*)\}\s*\+\s*_?\"", "\\1", str)
str = re.sub(r"\{([^}]*)\}\s*\+\s*_?\"", "\"\\1", str)
str = re.sub(r"\"\s*\+\s*\{([^}]*)\}", "\\1\"", str)
while str:
for type, regex in wml_regexes:
m = regex.match(str)
if m:
groups = m.groups()
str = str[m.end():]
if type in ("whitespace", "comment"):
pass
elif type == "keys":
yield from (("key", x) for x in zip(groups[0].split(","),
groups[1].split(",")))
elif type == "macro_open":
contents = groups[0]
count = 1
while count:
c = str[0]
if c == "{":
count += 1
if c == "}":
count -= 1
contents += c
str = str[1:]
yield "macro", (contents[1:-1],)
else:
yield type, groups
break
else:
raise RuntimeError("Can't parse {}".format(repr(str[:100])))
def parse_wml(tokens, tag_ann="all"):
keys = collections.defaultdict(WMLValue)
tags = collections.defaultdict(list)
macros = []
annotation = levels
tokens = iter(tokens)
for type, value in tokens:
if type == "key":
name, value = value
if name == "increse_attacks":
name = "increase_attacks"
for l in annotation:
setattr(keys[name], l, value)
if type == "open":
subtokens = []
nt = next(tokens)
count = 0
while count or nt != ("close", (value[0],)):
if nt == ("open", (value[0],)):
count += 1
if nt == ("close", (value[0],)):
count -= 1
subtokens.append(nt)
nt = next(tokens)
try:
tag = parse_wml(subtokens, annotation)
except Exception as e:
print(e.__class__)
raise
else:
tags[value[0]].append(tag)
if type == "close":
pass
if type == "macro":
if value[0].startswith("QUANTITY "):
_, name, easy, medium, hard = value[0].split()
keys[name].EASY = easy
keys[name].MEDIUM = medium
keys[name].HARD = hard
else:
macros.append(value[0])
if type == "pre":
if value[0] == "ifdef":
annotation = {value[1]}
elif value[0] == "else":
annotation = set(levels) - annotation
elif value[0] == "endif":
annotation = levels
elif value[0] == "define":
name = value[1].split()[0]
subtokens = []
nt = next(tokens)
while nt != ("pre", ("enddef", "")):
subtokens.append(nt)
nt = next(tokens)
tag = parse_wml(subtokens, annotation)
tags[name].append(tag)
return WMLTag(keys, tags, tag_ann, macros)
def format_parsed(tag, level=0):
stuff = []
for key, values in tag.keys.items():
if values.all():
stuff.append(" " * level + key + " = " + values.any)
else:
for name, value in values.iter():
stuff.append(" " * level + key + " = " + value + " # " + name)
for name, tags in tag.tags.items():
for tag in tags:
if tag.annotation != "all":
stuff.append(" " * level + "# " + tag.annotation)
stuff.append(" " * level + "[{}]".format(name))
stuff.append(format_parsed(tag, level=level + 1))
stuff.append(" " * level + "[/{}]".format(name))
return "\n".join(stuff)
def extract_abilities(start):
data = (start / "utils" / "abilities.cfg").open().read()
for type, obj in re.findall(r"#define ((?:ABILITY|WEAPON_SPECIAL)\S+)[^\n]*(.*?)#enddef", data, re.DOTALL):
try:
stuff = parse_wml(tokenize(obj))
except RuntimeError as e:
print(obj, "X" * 50, obj, "X" * 50, e, type(e))
raise e
for tag_type, tags in stuff.tags.items():
for tag in tags:
if tag.keys["name"].any:
if type.startswith("ABILITY"):
section = "Abilities"
else:
section = "Weapon Specials"
yield section, tag.keys["name"].any, tag_type, type, tag
#print(tag_type, tag.keys["name"].any)
#print(tag_type, tag.keys["description"].any)
#stuff = stuff.tags["object"][0]
#if "name" in stuff.keys and "filter" not in stuff.tags:
#yield stuff.keys["name"].any, stuff
def extract_items(start):
data = (start / "utils" / "item_list.cfg").open().read()
for obj in re.findall(r"\[object\].*?\[/object\]", data, re.DOTALL):
try:
stuff = parse_wml(tokenize(obj))
except RuntimeError as e:
print(f, "X" * 50, obj, "X" * 50, e, type(e))
raise e
stuff = stuff.tags["object"][0]
if "name" in stuff.keys and "filter" not in stuff.tags:
yield stuff.keys["name"].any, stuff
def extract_advancements(start):
for item in start.iterdir():
if item.is_dir():
yield from extract_advancements(item)
elif item.suffix == ".cfg":
yield from extract_from_file(item)
def extract_utils_amla_advancements(fname):
x = parse_wml(tokenize(fname.open().read()))
for name, tags in x.tags.items():
if name.endswith("ADVANCEMENTS") or name in ["ADDITIONAL_AMLA",
"LEGACY_DISCOVERY"]:
if name == "LEGACY_DISCOVERY":
name = "GENERIC_AMLA_ADVANCEMENTS"
for adv in tags[0].tags["advancement"]:
yield name, adv.keys["id"].any, adv
def extract_from_file(f):
data = f.open().read()
for adv in re.findall(r"\[advancement\].*?\[/advancement\]", data, re.DOTALL):
try:
stuff = parse_wml(tokenize(adv))
except RuntimeError as e:
print(f, "X" * 50, adv, "X" * 50, e, type(e))
raise e
stuff = stuff.tags["advancement"][0]
if "id" not in stuff.keys:
print(stuff.keys.keys())
yield f.stem, stuff.keys["id"].any, stuff
def format_values(values, positive, negative="",
percent=False, sort=False, invert=False):
s = ""
reverse = (all(i in "1234567890-" for n, v in values.iter() for i in v)
and values.any and (int(values.any) < 0 or invert))
for name, value in ([("", values.any)] if values.all else values.iter()):
if not value:
continue
if s:
s += ", "
if sort and value in sort_translations:
s += str(sort_translations[value])
elif reverse:
s += str(-int(value))
else:
s += str(value)
if percent:
s += "%"
if not values.all:
s += " (in {} difficulty)".format(name)
if reverse:
return negative.format(s)
return positive.format(s)
def item_key_sort(kv):
key, values = kv
if key == "sort":
return 0
if key == "damage":
return 1
if key == "attacks":
return 2
if key == "defence":
return 3
if key.endswith("resist"):
return 9
if key.endswith("penetrate"):
return 10
else:
return 11
def special_name(index, name, args):
if name in special_translation:
x = special_translation[name]
elif name == "PLAGUE_TYPE_LOTI":
x = "plague ({})".format(args[0])
elif name == "LESSER_BERSERK":
x = "lesser berserk ({})".format(args[0])
elif name == "EXTRA_5_IMPACT_DAMAGE":
x = "extra damage (+5; impact)"
else:
x = name.replace("_", " ").lower()
if "WEAPON_SPECIAL_" + name in index:
return "[[LotI_Abilities#{}|{}]]".format(index["WEAPON_SPECIAL_" + name], x)
return x
def ability_name(index, name, args):
args = [i.replace("_", "") for i in args]
if name in ability_translation:
x = ability_translation[name]
elif name.startswith("ABSORB"):
x = "absorbs ({})".format(name.replace("ABSORB_", ""))
elif name == "INCREASE_RESISTANCE_AURA":
x = "{} ({})".format(*args)
elif name == "EXTRA_DAMAGE_AURA":
x = "{} ({})".format(*args)
elif name == "BURNING_AURA":
x = "burns foes ({})".format(args[0])
elif name == "REGENERATES_OTHER":
x = "regenerates ({})".format(args[0])
elif name == "HEALS_OTHER":
x = "heals ({})".format(args[0])
elif name in ("SHIELD", "DESPAIR", "CONVICTION", "FRAIL_TIDE", "UNHOLYBANE"):
x = "{} ({})".format(name.replace("_", " ").lower(), args[0])
else:
x = name.replace("_", " ").lower()
if "ABILITY_" + name in index:
return "[[LotI_Abilities#{}|{}]]".format(index["ABILITY_" + name], x)
return x
class make_adv_index:
def __init__(self, advs):
self.index = {}
self.refs = set()
for section, name, tag in advs:
iname = name
ref = "{}_.E2.80.93_{}".format(tag.keys["description"].any.replace(" ", "_"),
iname.replace(" ", "_"))
i = 2
while ref in self.refs:
iname = "{}_{}".format(name, i)
ref = "{}_.E2.80.93_{}".format(tag.keys["description"].any.replace(" ", "_"),
iname.replace(" ", "_"))
i += 1
self.refs.add(ref)
self.index[section + name] = ref
if name in self.index:
self.index[name] = ""
else:
self.index[name] = ref
def get(self, section, id):
if id in self.index and self.index[id]:
return self.index[id]
if section + id in self.index:
return self.index[section + id]
return ""
def make_item_index(items):
index = {}
for name, tag in items:
index[name.lower()] = "{}_.E2.80.93_{}".format(name, format_values(tag.keys["sort"],
"{}", sort=True))
index["dark sword of desctruction"] = index["dark sword of destruction"]
index["dark helm of desctruction"] = index["dark helm of destruction"]
return index
def make_ability_index(items):
index = {}
for section, name, type, macro_name, tag in items:
index[macro_name] = "{}_.E2.80.93_{}".format(name, type)
return index
def write_item(name, tag, file, index):
def print(*a, **kw):
k = {"file": file, "end": "<br/>\n"}
k.update(kw)
return builtins.print(*a, **k)
sort = tag.keys["sort"].any
keys = tag.keys
print("===", name, "–", format_values(keys["sort"], "{}", sort=True), "===", end="\n")
if "flavour" in keys:
print("<span style='color:#808080'><i>{}</i></span>".format(keys["flavour"].any))
if "defence" in keys:
print(format_values(keys["defence"],
"<span style='color:#60A0FF'>Increases physical resistances by {}</span>",
"<span style='color:#60A0FF'>Decreases physical resistances by {}</span>",
percent=True))
for t in damage_ranges:
e = " ({} attacks only)".format(t.replace("_", "")) if t else t
if "damage" + t in keys:
print(format_values(keys["damage" + t],
"<span style='color:green'>Damage increased by {{}}{}</span>".format(e),
"<span style='color:green'>Damage decreased by {{}}{}</span>".format(e),
percent=True))
if "damage" + t + "_plus" in keys:
print(format_values(keys["damage" + t + "_plus"],
"<span style='color:green'>Damage increased by {{}}{}</span>".format(e),
"<span style='color:green'>Damage decreased by {{}}{}</span>".format(e)))
if "attacks" in keys:
print(format_values(keys["attacks"],
"<span style='color:green'>{} more attacks</span>",
"<span style='color:green'>{} fewer attacks</span>",
percent=sort in weapon_sorts))
if "merge" in keys:
print("<span style='color:green'>Merges attacks</span>")
if "damage_type" in keys:
print(format_values(keys["damage_type"],
"<span style='color:green'>Sets damage type to {}</span>"))
if "suck" in keys:
print(format_values(keys["suck"],
"<span style='color:#60A0FF'>Sucks {} health from targets with each hit</span>"))
if "spell_suck" in keys:
print(format_values(keys["spell_suck"],
"<span style='color:#60A0FF'>Spells suck {} health from targets with each hit</span>"))
if "devastating_blow" in keys:
print(format_values(keys["devastating_blow"],
"<span style='color:#60A0FF'>{} chance to strike a devastating blow</span>",
percent=True))
for t in damage_types:
if t + "_penetrate" in keys:
print(format_values(keys[t + "_penetrate"],
"<span style='color:green'>Enemy resistances to {} decreased by {{}}</span>".format(t),
percent=True))
for t in damage_ranges:
e = " ({} attacks only)".format(t.replace("_", "")) if t else t
for specials in tag.tags["specials" + t]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = special_name(name.replace("WEAPON_SPECIAL_", ""), args)
print("<span style='color:green'>New weapon special: {}{}</span>".format(real_name, e))
if "magic" in keys:
print(format_values(keys["magic"],
"<span style='color:green'>Increases all magical damages by {}</span>",
percent=True))
if "dodge" in keys:
print(format_values(keys["dodge"],
"<span style='color:#60A0FF'>Chance to get hit decreased by {}</span>",
percent=True))
for t in damage_types:
if t + "_resist" in keys:
print(format_values(keys[t + "_resist"],
"<span style='color:#60A0FF'>Resistance to {} increased by {{}}</span>".format(t),
"<span style='color:#60A0FF'>Resistance to {} decreased by {{}}</span>".format(t),
percent=True))
for effect in tag.tags["effect"]:
if effect.keys["apply_to"].any == "new_ability":
for specials in effect.tags["abilities"]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = ability_name(name.replace("ABILITY_", ""), args)
print("<span style='color:#60A0FF'>New ability: {}</span>".format(real_name))
elif effect.keys["apply_to"].any == "movement":
print(format_values(effect.keys["increase"],
"<span style='color:#60A0FF'>{} more movement points</span>",
"<span style='color:#60A0FF'>{} fewer movement points</span>"))
elif effect.keys["apply_to"].any == "vision":
print(format_values(effect.keys["vision"],
"<span style='color:#60A0FF'>Increases vision range by {}</span>",
"<span style='color:#60A0FF'>Decreases vision range by {}</span>"))
elif effect.keys["apply_to"].any == "hitpoints":
times = " per level" if "times" in effect.keys and effect.keys["times"].any == "per level" else ""
if "increase_total" in effect.keys:
print(format_values(effect.keys["increase_total"],
"<span style='color:#60A0FF'>{{}} more hitpoints per level{}</span>".format(times),
"<span style='color:#60A0FF'>{{}} fewer hitpoints per level{}</span>".format(times)))
if "heal_full" in effect.keys and effect.keys["heal_full"].any == "yes":
print("<span style='color=#60A0FF'>Full heal</span>")
elif effect.keys["apply_to"].any == "defence":
for defence in effect.tags["defence"]:
for m, h in defence_types:
if m in defence.keys:
print(format_values(defence.keys[m],
"<span style='color:#60A0FF'>Chance to get hit {} increased by {{}}</span>".format(h),
"<span style='color:#60A0FF'>Chance to get hit {} reduced by {{}}</span>".format(h),
percent=True))
elif effect.keys["apply_to"].any == "movement_costs":
for movement in effect.tags["movement_costs"]:
for m, h in movement_costs:
if m in movement.keys:
print(format_values(movement.keys[m],
"<span style='color:#60A0FF'>Movement costs {} set to {{}}</span>".format(h)))
elif effect.keys["apply_to"].any == "alignment":
print(format_values(effect.keys["alignment"],
"<span style='color:#60A0FF'>Sets alignment to {}</span>"))
elif effect.keys["apply_to"].any == "status" and effect.keys["add"].any == "not_living":
print("<span style='color:#60A0FF'>Unlife (immunity to poison, plague and drain)</span>")
elif effect.keys["apply_to"].any == "new_attack":
print("<span style='color:green'>New attack: {} ({} - {})</span>".format(effect.keys["name"].any,
effect.keys["damage"].any,
effect.keys["number"].any))
elif effect.keys["apply_to"].any == "new_advancement":
print("<span style='color:yellow'>New advancements: {}</span>".format(effect.keys["description"].any))
elif effect.keys["apply_to"].any in ["attack", "improve_bonus_attack"]:
bonus = effect.keys["apply_to"].any == "improve_bonus_attack"
range = wname = wtype = rt = ""
if "range" in effect.keys:
range = format_values(effect.keys["range"], "{}")
if "type" in effect.keys:
wtype = format_values(effect.keys["type"], "{}")
if range or wtype:
rt = " ({}{}{} attacks only)".format(range, " " if range and wtype else "", wtype)
if "name" in effect.keys:
wname = format_values(effect.keys["name"], " for the {} attack")
for specials in effect.tags["set_specials"]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = special_name(name.replace("WEAPON_SPECIAL_", ""), args)
print("<span style='color:green'>New weapon special{}{}: {}</span>".format(wname, rt, real_name))
if "remove_specials" in effect.keys:
print(format_values(effect.keys["remove_specials"],
"<span style='color:green'>Remove weapon special{}{}: {{}}</span>".format(wname, rt)))
if "increase_damage" in effect.keys:
print(format_values(effect.keys["increase_damage"],
"<span style='color:green'>Damage increased by {{}}{}{}</span>".format(wname, rt),
"<span style='color:green'>Damage decreased by {{}}{}{}</span>".format(wname, rt),
percent=bonus))
if "increase_attacks" in effect.keys:
print(format_values(effect.keys["increase_attacks"],
"<span style='color:green'>{{}} more attacks{}{}</span>".format(wname, rt),
"<span style='color:green'>{{}} fewer attacks{}{}</span>".format(wname, rt)))
if "set_type" in effect.keys:
print(format_values(effect.keys["set_type"],
"<span style='color:green'>Sets damage type to {{}}{}{}</span>".format(wname, rt)))
for latent in tag.tags["latent"]:
value = re.sub("\(requires ([^)]+)\)",
lambda m: "(requires [[#{}|{}]])".format(index[m.group(1).lower()], m.group(1)),
latent.keys["desc"].any)
print(re.sub("color='([^']+)'", "style='color:\\1'", value))
if "description" in keys:
v = re.sub("color='([^']+)'", "style='color:\\1'", keys["description"].any)
if "<" not in v:
v = "<span style='color:#808080'><i>{}</i></span>".format(v)
print(v)
print()
def write_advancement(section, name, tag, file, index):
def print(*a, **kw):
k = {"file": file, "end": "<br/>\n"}
k.update(kw)
return builtins.print(*a, **k)
keys = tag.keys
print("===", keys["description"].any, "–", name, "===", end="\n")
if "max_times" in keys:
print("<span style='color:#808080'><i>This advancement can be taken {} times</i></span>".format(keys["max_times"].any))
if "require_amla" in keys and keys["require_amla"].any not in ["{LEGACY}", ""]:
amlas = [n.strip() for n in keys["require_amla"].any.split(",")]
amlas = ", ".join("[[#{}|{}]]".format(index.get(section, n), n) if index.get(section, n) != "" else n for n in amlas)
print("<span style='color:#808080'><i>This advancement requires the advancements {} to be achieved first</i></span>".format(amlas))
for effect in tag.tags["effect"]:
if effect.keys["apply_to"].any == "new_ability":
for specials in effect.tags["abilities"]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = ability_name(name.replace("ABILITY_", ""), args)
print("<span style='color:#60A0FF'>New ability: {}</span>".format(real_name))
elif effect.keys["apply_to"].any == "remove_ability":
for specials in effect.tags["abilities"]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = ability_name(name.replace("ABILITY_", ""), args)
print("<span style='color:#60A0FF'>Remove ability: {}</span>".format(real_name))
elif effect.keys["apply_to"].any == "movement":
print(format_values(effect.keys["increase"],
"<span style='color:#60A0FF'>{} more movement points</span>",
"<span style='color:#60A0FF'>{} fewer movement points</span>"))
elif effect.keys["apply_to"].any == "vision":
print(format_values(effect.keys["vision"],
"<span style='color:#60A0FF'>Increases vision range by {}</span>",
"<span style='color:#60A0FF'>Decreases vision range by {}</span>"))
elif effect.keys["apply_to"].any == "hitpoints":
times = " per level" if "times" in effect.keys and effect.keys["times"].any == "per level" else ""
if "increase_total" in effect.keys:
print(format_values(effect.keys["increase_total"],
"<span style='color:#60A0FF'>{{}} more hitpoints per level{}</span>".format(times),
"<span style='color:#60A0FF'>{{}} fewer hitpoints per level{}</span>".format(times)))
if "heal_full" in effect.keys and effect.keys["heal_full"].any == "yes":
print("<span style='color=#60A0FF'>Full heal</span>")
elif effect.keys["apply_to"].any == "defence":
for defence in effect.tags["defence"]:
for m, h in defence_types:
if m in defence.keys:
print(format_values(defence.keys[m],
"<span style='color:#60A0FF'>Chance to get hit {} increased by {{}}</span>".format(h),
"<span style='color:#60A0FF'>Chance to get hit {} reduced by {{}}</span>".format(h),
percent=True))
elif effect.keys["apply_to"].any == "movement_costs":
for movement in effect.tags["movement_costs"]:
for m, h in movement_costs:
if m in movement.keys:
print(format_values(movement.keys[m],
"<span style='color:#60A0FF'>Movement costs {} set to {{}}</span>".format(h)))
elif effect.keys["apply_to"].any == "alignment":
print(format_values(effect.keys["alignment"],
"<span style='color:#60A0FF'>Sets alignment to {}</span>"))
elif effect.keys["apply_to"].any == "status" and effect.keys["add"].any == "not_living":
print("<span style='color:#60A0FF'>Unlife (immunity to poison, plague and drain)</span>")
elif effect.keys["apply_to"].any == "new_attack":
print("<span style='color:green'>New attack: {} ({} - {})</span>".format(effect.keys["name"].any,
effect.keys["damage"].any,
effect.keys["number"].any))
elif effect.keys["apply_to"].any == "new_advancement":
print("<span style='color:yellow'>New advancements: {}</span>".format(effect.keys["description"].any))
elif effect.keys["apply_to"].any in ["attack", "improve_bonus_attack"]:
bonus = effect.keys["apply_to"].any == "improve_bonus_attack"
range = wname = wtype = rt = ""
if "range" in effect.keys:
range = format_values(effect.keys["range"], "{}")
if "type" in effect.keys:
wtype = format_values(effect.keys["type"], "{}")
if range or wtype:
rt = " ({}{}{} attacks only)".format(range, " " if range and wtype else "", wtype)
if "name" in effect.keys:
wname = format_values(effect.keys["name"], " for the {} attack")
for specials in effect.tags["set_specials"]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = special_name(name.replace("WEAPON_SPECIAL_", ""), args)
print("<span style='color:green'>New weapon special{}{}: {}</span>".format(wname, rt, real_name))
if "remove_specials" in effect.keys:
print(format_values(effect.keys["remove_specials"],
"<span style='color:green'>Remove weapon special{}{}: {{}}</span>".format(wname, rt)))
if "increase_damage" in effect.keys:
print(format_values(effect.keys["increase_damage"],
"<span style='color:green'>Damage increased by {{}}{}{}</span>".format(wname, rt),
"<span style='color:green'>Damage decreased by {{}}{}{}</span>".format(wname, rt),
percent=bonus))
if "increase_attacks" in effect.keys:
print(format_values(effect.keys["increase_attacks"],
"<span style='color:green'>{{}} more attacks{}{}</span>".format(wname, rt),
"<span style='color:green'>{{}} fewer attacks{}{}</span>".format(wname, rt)))
if "set_type" in effect.keys:
print(format_values(effect.keys["set_type"],
"<span style='color:green'>Sets damage type to {{}}{}{}</span>".format(wname, rt)))
elif effect.keys["apply_to"].any == "resistance":
for resistances in effect.tags["resistance"]:
for t in damage_types:
if t in resistances.keys:
print(format_values(resistances.keys[t],
"<span style='color:#60A0FF'>Resistance to {} increased by {{}}</span>".format(t),
"<span style='color:#60A0FF'>Resistance to {} decreased by {{}}</span>".format(t),
percent=True, invert=True))
elif effect.keys["apply_to"].any == "defense":
for defence in effect.tags["defense"]:
for m, h in defence_types:
if m in defence.keys:
print(format_values(defence.keys[m],
"<span style='color:#60A0FF'>Chance to get hit {} increased by {{}}</span>".format(h),
"<span style='color:#60A0FF'>Chance to get hit {} reduced by {{}}</span>".format(h),
percent=True))
elif effect.keys["apply_to"].any == "bonus_attack":
print("<span style='color:green'>New bonus attack: {} ({}% - {} {})</span>".format(effect.keys["name"].any,
effect.keys["damage"].any,
effect.keys["range"].any,
effect.keys["type"].any))
wname = format_values(effect.keys["name"], " for the {} attack")
for specials in effect.tags["specials"]:
for special in specials.macros:
name, *args = shlex.split(special)
real_name = special_name(name.replace("WEAPON_SPECIAL_", ""), args)
print("<span style='color:green'>New weapon special{}: {}</span>".format(wname, real_name))
print()
def write_ability(section, name, type, macro_name, tag, file):
def print(*a, **kw):
k = {"file": file, "end": "<br/>\n"}
k.update(kw)
return builtins.print(*a, **k)
keys = tag.keys
print("===", keys["name"].any, "–", type, "===", end="\n")
print(re.sub("([^\n]+)", "<span style='color:#808080'><i>\\1</i></span>", keys["description"].any))
print()
def main():
global ability_name, special_name
parser = argparse.ArgumentParser()
parser.add_argument("LoTIdir")
args = parser.parse_args()
start = pathlib.Path(args.LoTIdir).resolve()
print("LotI Scraper version", __version__, "loading from directory", start)
print("Scanning info...")
info = parse_wml(tokenize((start / "_info.cfg").open().read()))
version = info.tags["info"][0].keys["version"].any
print("LotI version is", version)
def sort_by_first(x):
if x[0] == "GENERIC_AMLA_ADVANCEMENTS":
return "0" + x[0]
if x[0] == "ADDITIONAL_AMLA":
return "1" + x[0]
if x[0][0] == "S":
return "2" + x[0]
return "3" + x[0]
def sort_by_not_last(x):
return x[:-1]
print("Scanning standard advancements...")
advancements_standard = list(extract_utils_amla_advancements(start / "utils" / "amla.cfg"))
advancements_standard.sort(key=sort_by_first)
print("Scanning unit advancements...")
advancements_units = list(extract_advancements(start / "units"))
advancements_units.sort(key=sort_by_first)
print("Scanning abilities...")
abilities = list(extract_abilities(start))
abilities.sort(key=sort_by_not_last)
ability_index = make_ability_index(abilities)
def ability_name(*args, old=ability_name):
return old(ability_index, *args)
def special_name(*args, old=special_name):
return old(ability_index, *args)
print("Scanning items...")
items = list(extract_items(start))
items.sort(key=sort_by_not_last)
print("Found", len(abilities), "abilities,",len(advancements_standard), "standard advancements,",
len(advancements_units), "unit advancements and", len(items), "items")
print("Writing item information to items.wiki")
with open("items.wiki", "w") as items_file:
print(header.format("all the items", "", version), file=items_file)
index = make_item_index(items)
for item in items:
write_item(*item, items_file, index)
print("Writing ability information to abilities.wiki")
with open("abilities.wiki", "w") as ability_file:
print(header.format("all the abilities and weapon specials", "", version), file=ability_file)
#index = make_item_index(items)
for section, abilities in itertools.groupby(abilities, lambda x: x[0]):
print("==", section, "==", file=ability_file)
for ab in abilities:
write_ability(*ab, ability_file)
print("Writing standard advancement information to advancements_standard.wiki")
with open("advancements_standard.wiki", "w") as adv_standard_file:
print(header.format("all the advancements avalible for catagories of units",
"See https://wiki.wesnoth.org/LotI_Unit_Advancements for unit specific advancements.",
version), file=adv_standard_file)
index = make_adv_index(advancements_standard)
for section, advs in itertools.groupby(advancements_standard, sort_by_first):
section = section[1:]
if section == "GENERIC_AMLA_ADVANCEMENTS":
section = "Legacies and Books"
elif section == "ADDITIONAL_AMLA":
section = "Soul Eater and God Advancements"
else:
section = section.replace("_", " ").replace("AMLA ", "").title()
print("==", section, "==", file=adv_standard_file)
print(file=adv_standard_file)
for adv in advs:
write_advancement(*adv, adv_standard_file, index)
print(file=adv_standard_file)
print("Writing unit advancement information to advancements_units.wiki")
with open("advancements_units.wiki", "w") as adv_units_file:
print(header.format("all the advancements that are unit specific",
"See https://wiki.wesnoth.org/LotI_Standard_Advancements for general advancements such as legacies and books.",
version), file=adv_units_file)
index = make_adv_index(advancements_units)
for section, advs in itertools.groupby(advancements_units, sort_by_first):
section = section[1:].replace("_", " ").replace("AMLA ", "").title()
print("==", section, "==", file=adv_units_file)
print(file=adv_units_file)
for adv in advs:
write_advancement(*adv, adv_units_file, index)
print(file=adv_units_file)
print("All done!")
if __name__ == "__main__":
main()
Raise to rise maybe?Widowmaker, Widowermaker:
The number of widows will raise after this battle.
Also, did you see viewtopic.php?f=23&t=32370 as some of those may be useful, due to the amount of magic / magic-based attacks in LotI
Re: Legend of the Invincibles
I have an idea for a new class of specials.
Something like a goblin whip master. But essentially could be any drummer or such. Idea is that they give additional movement to the units around them at the beginning of the turn or at the end for the next turn. Maybe even 3 levels are in order (+1,+2,+3). That way you could get slow units through a fjord, snow or a cave.
Just dropping it here because you are the one who could make such a thing.
Something like a goblin whip master. But essentially could be any drummer or such. Idea is that they give additional movement to the units around them at the beginning of the turn or at the end for the next turn. Maybe even 3 levels are in order (+1,+2,+3). That way you could get slow units through a fjord, snow or a cave.
Just dropping it here because you are the one who could make such a thing.
Re: Legend of the Invincibles
"They run as if the very whips of their master were behind them."
Might work. The extra moves should be added at the start of the turn IMO. It would be like a springboard for attacking faster. Would be good for HI crossing a stream...
Re: Legend of the Invincibles
@matsjoyce
Thanks for the good work. I have updated the initial post with the new page and the new version of the script.
@Wussel
It might have a tactical effect and it should be trivial to code, but its theme sounds a bit awkward. The heroes advocate the use of undead in battle instead of normal fighters because they do not suffer, whipping their soldiers does not sound like something they would do. Can you please come up with a better explanation what makes your own units run like if hellhounds were on their trails? Maybe that the unit with the ability is so horrifying that its allies prefer to flee (Efraim and Lethalia, incarnations of grim stygian darkness, would not notice)?
Thanks for the good work. I have updated the initial post with the new page and the new version of the script.
@Wussel
It might have a tactical effect and it should be trivial to code, but its theme sounds a bit awkward. The heroes advocate the use of undead in battle instead of normal fighters because they do not suffer, whipping their soldiers does not sound like something they would do. Can you please come up with a better explanation what makes your own units run like if hellhounds were on their trails? Maybe that the unit with the ability is so horrifying that its allies prefer to flee (Efraim and Lethalia, incarnations of grim stygian darkness, would not notice)?
Re: Legend of the Invincibles
Dugi wrote:its theme sounds a bit awkward
I think there's nothing wrong with the drummer theme; maybe call it 'marching drums' or something.Wussel wrote:essentially could be any drummer or such
Spoiler:
I have a cunning plan.
-
- Posts: 189
- Joined: February 16th, 2015, 5:02 pm
Re: Legend of the Invincibles
I like the goblin suggestion.
I think it would be nice, if every race captain had one ability, noone else would.
For example Elvish Marshal could have aura of strugle, as elves are more nimble based unit, then resistace based.
Lich king could have something like aura of sucking +1 suck/or aura of leechces
Dwarf king could have the Hate speech
And The goblin can have marching drums...
I think it would be nice, if every race captain had one ability, noone else would.
For example Elvish Marshal could have aura of strugle, as elves are more nimble based unit, then resistace based.
Lich king could have something like aura of sucking +1 suck/or aura of leechces
Dwarf king could have the Hate speech
And The goblin can have marching drums...
Re: Legend of the Invincibles
Okay, so it shall be the drummer.
This issue is gone?I wrote:The investigation of the save file of a leader not attacking even a completely vulnerable enemy has shown that it was an old save file that did not have the custom AI enabled. Therefore, it would be helpful to me if someone attached a save file where a leader does not attack an unprotected unit (he should not rush into a group of your units)?
Re: Legend of the Invincibles
I had found something similar to that.Wussel wrote:I have an idea for a new class of specials.
Something like a goblin whip master. But essentially could be any drummer or such. Idea is that they give additional movement to the units around them at the beginning of the turn or at the end for the next turn. Maybe even 3 levels are in order (+1,+2,+3). That way you could get slow units through a fjord, snow or a cave.
Just dropping it here because you are the one who could make such a thing.
My usage for it was intended for Grand Knight, to give the adjacent half of their maximum movement as temporary extra moves at turn begin, but only for other loyalist cavalry types. I have not edited it yet to become as such, for now it just gives +2 movement to any adjacent type. Take use if you want.
Code: Select all
#define ABILITY_CUSTOM_INSPIRATION
[dummy]
id=custom_inspiration
name= _ "inspiration"
description= _ "inspires adjacent units to move 2 hex further."
[/dummy]
[/abilities]
[event]
name=turn refresh # After the normal restoration of movement
first_time_only=no
id=custom_inspiration_movement_event
[store_unit]
[filter]
side=$side_number
[filter_adjacent] # Grab all units standing next to units with inspiration
ability=custom_inspiration
[filter_side] # Don't get inspired by enemies
is_enemy=no
[/filter_side]
[/filter_adjacent]
[/filter]
variable=inspired
[/store_unit]
{FOREACH inspired i} # Repeat this for every inspired unit
{VARIABLE_OP inspired[$i].moves add 2} # Add 2 movement point to the moves restored to maximum
[unstore_unit] # Return it back to the game
variable=inspired[$i]
find_vacant=no
[/unstore_unit]
{NEXT i}
{CLEAR_VARIABLE inspired} # Clean up
[/event]
[+abilities]
#enddef
Trommler for unit and Kriegstrommeln for ability ?In German the guy could be called 'Tambour', didn't find a translation...
Re: Legend of the Invincibles
to me "Tambour" sounds better than "Trommler" ...Eagle_11 wrote: Trommler for unit and Kriegstrommeln for ability ?
or you could call it a "Goblin Cheerleader"
i'm sure this would be pretty cute :3
may the source be with you
=(^.^)= nyan~
=(^.^)= nyan~