update
This commit is contained in:
@@ -161,12 +161,16 @@ def unit_stats(unit: dict, stars: int, item_apis: list[str], static_items: dict,
|
||||
dmg *= 1 + acc["ap_flat"] / 100
|
||||
if scaling in ("ad", "both"):
|
||||
dmg *= 1 + acc["ad_pct"]
|
||||
frontline = FRONTLINE_MANA_PER_SEC if role == "frontline" else 0
|
||||
cast_rate = min(
|
||||
(as_eff * MANA_PER_ATTACK + frontline + acc["mana_regen"]) / mana_gap,
|
||||
CAST_RATE_CAP,
|
||||
)
|
||||
spell_dps = dmg * cast_rate
|
||||
if unit.get("spell_on_attack"):
|
||||
# Passive: Bonus-Schaden pro Auto-Attacke, kein Mana-Zyklus.
|
||||
spell_dps = dmg * as_eff
|
||||
else:
|
||||
frontline = FRONTLINE_MANA_PER_SEC if role == "frontline" else 0
|
||||
cast_rate = min(
|
||||
(as_eff * MANA_PER_ATTACK + frontline + acc["mana_regen"]) / mana_gap,
|
||||
CAST_RATE_CAP,
|
||||
)
|
||||
spell_dps = dmg * cast_rate
|
||||
# Ausreißer-Guard: Spell-Rohwerte sind zwischen Champions nicht
|
||||
# vergleichbar (per-Hit vs. total). Relativer Deckel: max. 3× eigene
|
||||
# Auto-DPS; das Populations-Perzentil dient als Floor für AD-lose Caster.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Scripted policies playing full games headless — sanity check for the sim."""
|
||||
|
||||
from tft.sim import policy
|
||||
from tft.sim import player, policy
|
||||
from tft.sim.game import Game
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ def play_econ(game: Game) -> int:
|
||||
while not game.over:
|
||||
policy.act(game.player, game.pool, game.artifact, game.cfg, game.rng,
|
||||
game.round, params)
|
||||
# Skript-Spieler stellt wie ein Bot auf (step tauscht für Menschen nicht).
|
||||
player.fill_board(game.player, game.artifact)
|
||||
game.step()
|
||||
return game.placement
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ class Game:
|
||||
for p in self.players:
|
||||
p.shop = [None] * cfg["shop"]["slots"]
|
||||
self._maybe_offer_augment()
|
||||
self._bots_act()
|
||||
|
||||
# ---- helpers ----
|
||||
|
||||
@@ -81,6 +82,15 @@ class Game:
|
||||
if p is self.player:
|
||||
self.log.append(f"{self.round['label']}: Carousel")
|
||||
|
||||
def _bots_act(self) -> None:
|
||||
"""Bots ziehen beim Rundenstart — ihre Boards und Scores stehen damit
|
||||
für die ganze Planungsphase fest und kämpfen unverändert."""
|
||||
for b in self.bots:
|
||||
if b.alive:
|
||||
policy.act(b, self.pool, self.artifact, self.cfg, self.rng,
|
||||
self.round, policy.ARCHETYPES[b.archetype])
|
||||
player.fill_board(b, self.artifact)
|
||||
|
||||
def _maybe_offer_augment(self) -> None:
|
||||
if not self.round.get("augment"):
|
||||
return
|
||||
@@ -187,12 +197,10 @@ class Game:
|
||||
self.pick_augment(self.rng.randrange(len(self.player.augment_offer)))
|
||||
|
||||
rnd = self.round
|
||||
for b in self.bots:
|
||||
if b.alive:
|
||||
policy.act(b, self.pool, self.artifact, self.cfg, self.rng, rnd,
|
||||
policy.ARCHETYPES[b.archetype])
|
||||
for p in self._alive():
|
||||
player.fill_board(p, self.artifact)
|
||||
# Bots haben schon beim Rundenstart gezogen (_bots_act).
|
||||
# swap=False: die manuelle Aufstellung des Spielers bleibt unangetastet.
|
||||
if self.player.alive:
|
||||
player.fill_board(self.player, self.artifact, swap=False)
|
||||
|
||||
results = self._resolve(rnd)
|
||||
self._check_eliminations()
|
||||
@@ -308,6 +316,7 @@ class Game:
|
||||
continue
|
||||
player.refresh_shop(p, self.pool, self.cfg, self.rng)
|
||||
self._maybe_offer_augment()
|
||||
self._bots_act()
|
||||
|
||||
def _finish_by_hp(self) -> None:
|
||||
standings = sorted(self._alive(), key=lambda p: -p.hp)
|
||||
|
||||
@@ -194,11 +194,15 @@ def grab_carousel_unit(p: PlayerState, pool: Pool, rng: random.Random) -> None:
|
||||
merge(p, api, 1)
|
||||
|
||||
|
||||
def fill_board(p: PlayerState, artifact: dict) -> None:
|
||||
"""Freie Board-Plätze auffüllen und stärkere Bank-Units einwechseln."""
|
||||
def fill_board(p: PlayerState, artifact: dict, swap: bool = True) -> None:
|
||||
"""Freie Board-Plätze auffüllen; mit swap auch stärkere Bank-Units einwechseln.
|
||||
|
||||
swap=False für den Menschen: seine Aufstellung wird nie überstimmt."""
|
||||
p.bench.sort(key=lambda u: -unit_worth(artifact, u))
|
||||
while len(p.board) < p.level and p.bench:
|
||||
p.board.append(p.bench.pop(0))
|
||||
if not swap:
|
||||
return
|
||||
for i, u in enumerate(p.board):
|
||||
if not p.bench:
|
||||
break
|
||||
|
||||
@@ -18,6 +18,10 @@ DAMAGE_FALLBACKS = (
|
||||
)
|
||||
TAG_TO_TYPE = {"magicDamage": "magic", "physicalDamage": "physical", "trueDamage": "true"}
|
||||
|
||||
# "Attacks deal <tag>@X@" (Markup dazwischen erlaubt) — Schaden pro Auto-Attacke.
|
||||
ATTACK_CTX_RE = re.compile(r"attacks deal\s*(?:<[^>]+>\s*)*$", re.I)
|
||||
ATTACK_VAR_RE = re.compile(r"OnAttack|ToAttack|PerAttack")
|
||||
|
||||
# Augment-Stufe steckt im Icon-Dateinamen: _I / _II / _III.
|
||||
AUGMENT_TIER_RE = re.compile(r"[-_](I{1,3})\.", re.I)
|
||||
PLACEHOLDER_RE = re.compile(r"@([A-Za-z0-9_]+)(?:\*([\d.]+))?@")
|
||||
@@ -62,12 +66,18 @@ def resolve_spell(ability: dict) -> dict:
|
||||
|
||||
dmg_type = None
|
||||
array = None
|
||||
on_attack = False
|
||||
for m in DAMAGE_TAG_RE.finditer(desc):
|
||||
for vm in VAR_RE.finditer(m.group(2)):
|
||||
name = vm.group(1)
|
||||
array = get(name) or get(name.removeprefix("Modified"))
|
||||
if array:
|
||||
dmg_type = TAG_TO_TYPE[m.group(1)]
|
||||
# Schaden pro Auto-Attacke statt pro Cast (z.B. Diana, Teemo):
|
||||
# erkennbar am Variablennamen oder an "Attacks deal" vorm Tag.
|
||||
context = desc[max(0, m.start() - 60):m.start()]
|
||||
on_attack = bool(ATTACK_VAR_RE.search(name)
|
||||
or ATTACK_CTX_RE.search(context))
|
||||
break
|
||||
if array:
|
||||
break
|
||||
@@ -80,6 +90,7 @@ def resolve_spell(ability: dict) -> dict:
|
||||
for name in DAMAGE_FALLBACKS:
|
||||
array = get(name)
|
||||
if array:
|
||||
on_attack = bool(ATTACK_VAR_RE.search(name))
|
||||
break
|
||||
|
||||
has_ap = "%i:scaleAP%" in desc
|
||||
@@ -90,6 +101,7 @@ def resolve_spell(ability: dict) -> dict:
|
||||
"spell_damage": array,
|
||||
"spell_damage_type": dmg_type or ("magic" if array else None),
|
||||
"spell_scaling": scaling,
|
||||
"spell_on_attack": on_attack,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user