diff --git a/.gitignore b/.gitignore
index 7c74fd54e..050177101 100644
--- a/.gitignore
+++ b/.gitignore
@@ -96,12 +96,10 @@ ValheimUnity/Assets/ExternalLibraries/EpicLoot-UnityLib.dll
ValheimUnity/Packages/packages-lock.json
# Manual adds
-Common
ValheimMods.sln
*.env
/EpicLoot/shardstone_testing_matrix.csv
/.gitignore
-/Common/Common.projitems
/EpicLoot/EpicLoot.csproj
/EpicLoot/EpicLoot.sln
/EpicLoot/shardstone_testing_matrix.csv
diff --git a/Common/src/Modding/ConfigFileReloader.cs b/Common/src/Modding/ConfigFileReloader.cs
new file mode 100644
index 000000000..3083b19f5
--- /dev/null
+++ b/Common/src/Modding/ConfigFileReloader.cs
@@ -0,0 +1,370 @@
+using BepInEx.Configuration;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+namespace Common {
+ ///
+ /// Reloads files the owning mod reads from disk when they change: its BepInEx .cfg (see
+ /// ) and any other file a caller registers with , such as
+ /// EpicLoot's baseconfig json files.
+ ///
+ /// Driven by each file's timestamp+size rather than directly by FileSystemWatcher events, for two
+ /// reasons that both only bite on Linux servers:
+ ///
+ /// Trailing edge, not leading. One save is a burst of events, and on Linux the first of them
+ /// arrives as soon as the writer truncates the file - before any of the new content is there.
+ /// Reloading on that first event and then ignoring the rest of the burst (a leading-edge debounce,
+ /// which is what both mods used to do) reads an empty or half written file and never looks again,
+ /// so the edit appears to be ignored until the next restart. It reads clean on Windows only by
+ /// accident: the writer holds a share lock there, the partial read throws, and a later event in
+ /// the same burst retries. Worse, applying a half read file with SaveOnConfigSet on writes the
+ /// merged result straight back over the file the admin is still uploading. So: wait for the file
+ /// to stop changing, then read it once.
+ ///
+ /// Poll, not events alone. inotify only reports writes made through the same mount, so a config
+ /// folder the host exposes over NFS/SFTP or a container bind mount can be edited without the game
+ /// ever seeing an event. The stat below costs nothing and makes the reload work regardless; the
+ /// watcher events just make it react in half a second instead of two.
+ ///
+ /// Files are applied in registration order, and a burst that touches several files is applied in
+ /// one pass once the last of them holds still, so a folder upload lands in the dependency order
+ /// the registrations encode rather than the order the writes happened to finish in.
+ ///
+ /// Common is a shared project, so the statics here belong to one mod assembly, not to all of them.
+ ///
+ public class ConfigFileReloader : MonoBehaviour {
+ /// One registered file and what is known about its state on disk.
+ public sealed class WatchedFile {
+ /// Absolute path of the file.
+ public string Path { get; }
+
+ ///
+ /// Reads the file into the live config. Exceptions are caught and logged by the scheduler.
+ ///
+ public Func Reload { get; }
+
+ ///
+ /// Whether an edit is applied while this machine is a client on someone else's server.
+ /// False means the file obeys the owner rule (see ): the edit
+ /// is skipped while connected and applied after disconnecting.
+ ///
+ public bool ReloadOnConnectedClient { get; }
+
+ // The state of the file as last read into the live config. A length of -1 never matches a
+ // real stat, so the next check treats the file as changed (see MarkDirty).
+ internal DateTime AppliedWriteTimeUtc;
+ internal long AppliedLength = -1;
+
+ // The state the previous check observed, whether or not it was applied. Distinguishes a
+ // file that actually changed on disk from one whose applied stamp was merely forgotten.
+ internal DateTime SeenWriteTimeUtc;
+ internal long SeenLength = -1;
+
+ // The state seen by the previous check, while waiting for it to settle, and the time at
+ // which it counts as settled if it is still the same then.
+ internal bool ChangePending;
+ internal DateTime PendingWriteTimeUtc;
+ internal long PendingLength;
+ internal float PendingSettledAt;
+
+ // So a client connected to a server hears once that its edit is waiting, not once per poll.
+ internal bool SkipLogged;
+
+ internal WatchedFile(string path, Func reload, bool reloadOnConnectedClient) {
+ Path = path;
+ Reload = reload;
+ ReloadOnConnectedClient = reloadOnConnectedClient;
+ }
+ }
+
+ /// How long a file must hold still before it is read.
+ private const float QuietPeriodSeconds = 0.5f;
+
+ /// How often the files are checked with no watcher event to prompt it.
+ private const float PollIntervalSeconds = 2f;
+
+ private static ConfigFileReloader instance;
+
+ ///
+ /// Set from a FileSystemWatcher callback, which may run on an arbitrary thread; consumed in
+ /// Update. A plain flag rather than a scheduled call so it stays correct no matter which
+ /// thread the event lands on.
+ ///
+ private static volatile bool checkRequested;
+
+ // Registration order. The scheduler applies changed files in this order.
+ private readonly List files = new List();
+
+ private float nextCheck;
+
+ ///
+ /// Starts reloading the mod's BepInEx from
+ /// when it changes. Safe to call more than once.
+ ///
+ public static void Begin(ConfigFile config, string configFilePath) {
+ Watch(configFilePath, () => ReloadConfigFile(config));
+ }
+
+ ///
+ /// Registers and stats it now: the file as it stands is what is already
+ /// in memory, so call this right after the initial read and only later edits reload. Registering
+ /// a path again replaces its entry in place, keeping its position in the apply order.
+ ///
+ /// Reads the file into the live config.
+ ///
+ /// True to apply edits even while connected to someone else's server; false (the default) to
+ /// leave the server's copy in effect until disconnecting.
+ ///
+ public static WatchedFile Watch(string path, Func reload, bool reloadOnConnectedClient = false) {
+ ConfigFileReloader host = EnsureInstance();
+ WatchedFile file = new WatchedFile(path, reload, reloadOnConnectedClient);
+ (file.AppliedWriteTimeUtc, file.AppliedLength) = Stat(path);
+ file.SeenWriteTimeUtc = file.AppliedWriteTimeUtc;
+ file.SeenLength = file.AppliedLength;
+
+ int existing = host.IndexOf(path);
+ if (existing >= 0) {
+ host.files[existing] = file;
+ } else {
+ host.files.Add(file);
+ }
+
+ return file;
+ }
+
+ ///
+ /// Records the file as it stands now as what is in memory. For code that wrote the file itself
+ /// and already read it back, so the scheduler does not apply it a second time. No-op for a path
+ /// that is not registered.
+ ///
+ public static void MarkApplied(string path) {
+ WatchedFile file = Find(path);
+ if (file == null) { return; }
+
+ (file.AppliedWriteTimeUtc, file.AppliedLength) = Stat(path);
+ file.SeenWriteTimeUtc = file.AppliedWriteTimeUtc;
+ file.SeenLength = file.AppliedLength;
+ file.ChangePending = false;
+ file.SkipLogged = false;
+ }
+
+ ///
+ /// Forgets what was applied: memory no longer reflects the file (a server pushed its own copy).
+ /// The file is read again on the next check that is allowed to apply it - on a connected client
+ /// that is the first check after disconnecting, which is what restores the local configs then.
+ /// The file has not changed on disk, so this is not reported as an edit. No-op for a path that
+ /// is not registered.
+ ///
+ public static void MarkDirty(string path) {
+ WatchedFile file = Find(path);
+ if (file == null) { return; }
+
+ file.AppliedWriteTimeUtc = default;
+ file.AppliedLength = -1;
+ file.ChangePending = false;
+ file.SkipLogged = false;
+ }
+
+ /// Brings the next check forward to the following frame. Callable from any thread.
+ public static void CheckSoon() {
+ checkRequested = true;
+ }
+
+ ///
+ /// Whether this machine owns what is in its config files.
+ ///
+ /// Mirrors Jotunn's own ReadWriteConfigFromDisk: no ZNet means the main menu, IsServer covers a
+ /// dedicated server, a host and singleplayer. A connected client is left out because its
+ /// server-synced entries hold the server's values, not the ones on its disk.
+ ///
+ /// For the .cfg this is belt and braces rather than the only guard: Jotunn already prefixes
+ /// ConfigEntryBase.SetSerializedValue to drop disk writes to admin-only (IsAdminOnly, i.e.
+ /// ConfigBinder.BindServerConfig) entries on a connected client, so those cannot be clobbered
+ /// by a reload in any case. What this adds is that a client does not reload at all, which also
+ /// keeps every file save from waking Jotunn's SynchronizeChangedConfig. The cost is that
+ /// client-local (BindClientConfig) entries stop hot-reloading while connected to someone
+ /// else's server; the pending edit is applied on disconnect, since the stamp is deliberately
+ /// left unrecorded while this returns false. Files registered with
+ /// reloadOnConnectedClient: true opt out of the rule.
+ ///
+ private static bool OwnsConfigOnDisk() {
+ return ZNet.instance == null || ZNet.instance.IsServer();
+ }
+
+ private static ConfigFileReloader EnsureInstance() {
+ // Unity's null check, not C#'s: the host object is destroyed if the game ever tears down
+ // DontDestroyOnLoad, and the stale reference then compares equal to null and is replaced.
+ if (instance != null) { return instance; }
+
+ // Named per mod: every Common consumer gets its own copy of this type, so a scene dump
+ // would otherwise show several identically named objects.
+ GameObject host = new GameObject($"ConfigFileReloader ({ModContext.PluginGuid})");
+ DontDestroyOnLoad(host);
+
+ instance = host.AddComponent();
+ instance.nextCheck = Time.realtimeSinceStartup + PollIntervalSeconds;
+ return instance;
+ }
+
+ private static WatchedFile Find(string path) {
+ if (instance == null) { return null; }
+
+ int index = instance.IndexOf(path);
+ return index >= 0 ? instance.files[index] : null;
+ }
+
+ private int IndexOf(string path) {
+ for (int i = 0; i < files.Count; i++) {
+ if (string.Equals(files[i].Path, path, StringComparison.OrdinalIgnoreCase)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private void Update() {
+ float now = Time.realtimeSinceStartup;
+ if (checkRequested) {
+ checkRequested = false;
+ nextCheck = now;
+ }
+
+ if (now < nextCheck) { return; }
+
+ bool owner = OwnsConfigOnDisk();
+ bool unsettled = false;
+ List due = null;
+
+ foreach (WatchedFile file in files) {
+ (DateTime writeTimeUtc, long length) = Stat(file.Path);
+ bool changedOnDisk = writeTimeUtc != file.SeenWriteTimeUtc || length != file.SeenLength;
+ file.SeenWriteTimeUtc = writeTimeUtc;
+ file.SeenLength = length;
+
+ if (length < 0) {
+ // Gone for the moment - mid rename, or deleted. Nothing to read; when it comes back
+ // its stamp will differ from the applied one and this picks it up then.
+ file.ChangePending = false;
+ continue;
+ }
+
+ if (writeTimeUtc == file.AppliedWriteTimeUtc && length == file.AppliedLength) {
+ file.ChangePending = false;
+ continue;
+ }
+
+ if (!owner && !file.ReloadOnConnectedClient) {
+ // The server's copy is in effect. The applied stamp is deliberately left alone so
+ // the edit is picked up by the first check after disconnecting. Reported only for a
+ // real edit, not for a file whose stamp MarkDirty forgot after a server push.
+ file.ChangePending = false;
+ if (changedOnDisk && !file.SkipLogged) {
+ ModLogger.LogInfo($"Config file {file.Path} changed on disk, but this client is " +
+ "connected to a server whose copy is in effect; the edit is applied after disconnecting.");
+ file.SkipLogged = true;
+ }
+ continue;
+ }
+
+ if (file.ChangePending && writeTimeUtc == file.PendingWriteTimeUtc && length == file.PendingLength) {
+ // Same expression nextCheck is set from below, so a check that is due never finds
+ // a file a rounding error short of settled.
+ if (now >= file.PendingSettledAt) {
+ (due ??= new List()).Add(file);
+ } else {
+ unsettled = true;
+ }
+ continue;
+ }
+
+ // Changed since the last read, or still being written. Only act once it has held the
+ // same stamp for a whole quiet period, so a file mid-write is left alone until the
+ // writer is done with it.
+ file.ChangePending = true;
+ file.PendingWriteTimeUtc = writeTimeUtc;
+ file.PendingLength = length;
+ file.PendingSettledAt = now + QuietPeriodSeconds;
+ unsettled = true;
+ }
+
+ if (unsettled) {
+ // Wait for the whole burst: a folder upload is applied in one ordered pass once the
+ // last file holds still, not file by file as the writes happen to finish.
+ nextCheck = now + QuietPeriodSeconds;
+ return;
+ }
+
+ nextCheck = now + PollIntervalSeconds;
+ if (due == null) { return; }
+
+ foreach (WatchedFile file in due) {
+ Reload(file);
+ }
+ }
+
+ private static void Reload(WatchedFile file) {
+ (DateTime beforeWriteTimeUtc, long beforeLength) = Stat(file.Path);
+
+ // Info, not Debug: on a server this line is the only evidence an admin's edit was picked up.
+ // An unknown applied stamp means the file did not change; memory did (a server's copy was
+ // in effect until now, or the file only just appeared), so say that instead.
+ ModLogger.LogInfo(file.AppliedLength < 0
+ ? $"Reading config file {file.Path} from disk."
+ : $"Config file {file.Path} changed on disk, reloading it.");
+ try {
+ file.Reload();
+ } catch (Exception e) {
+ ModLogger.LogWarning($"There was an issue loading {System.IO.Path.GetFileName(file.Path)}.\n{e.Message}");
+ }
+
+ (DateTime afterWriteTimeUtc, long afterLength) = Stat(file.Path);
+ file.ChangePending = false;
+ file.SkipLogged = false;
+ file.SeenWriteTimeUtc = afterWriteTimeUtc;
+ file.SeenLength = afterLength;
+
+ if (afterWriteTimeUtc == beforeWriteTimeUtc && afterLength == beforeLength) {
+ // Recorded even when the read failed, so a file that cannot be parsed is attempted
+ // once per edit instead of on every poll.
+ file.AppliedWriteTimeUtc = afterWriteTimeUtc;
+ file.AppliedLength = afterLength;
+ return;
+ }
+
+ // The file changed underneath the reload: a save that landed mid-read, or a reload handler
+ // that wrote back. The applied stamp is left alone, so the next check sees a new pending
+ // change and reads again after a quiet period. A write-back converges: the second read
+ // yields the values just applied, so nothing changes and nothing is written again.
+ }
+
+ /// The .cfg reload registered by .
+ private static bool ReloadConfigFile(ConfigFile config) {
+ bool saveOnConfigSet = config.SaveOnConfigSet;
+ try {
+ // Disk is the source of truth for this pass, so suppress the write-back each changed
+ // entry would otherwise trigger: it would rewrite the file we are reading and re-trip
+ // every watcher for no change in content.
+ config.SaveOnConfigSet = false;
+ config.Reload();
+ return true;
+ } finally {
+ config.SaveOnConfigSet = saveOnConfigSet;
+ }
+ }
+
+ /// Timestamp and size of the file, or (default, -1) when it does not exist.
+ private static (DateTime WriteTimeUtc, long Length) Stat(string path) {
+ try {
+ FileInfo info = new FileInfo(path);
+ return info.Exists ? (info.LastWriteTimeUtc, info.Length) : (default, -1L);
+ } catch (Exception) {
+ // A stat can fail on its own (permissions, a path that stopped being a file). Treat it
+ // as absent; the next check tries again.
+ return (default, -1L);
+ }
+ }
+ }
+}
diff --git a/EpicLoot/EpicLoot.cs b/EpicLoot/EpicLoot.cs
index 361f4baf6..26138711b 100644
--- a/EpicLoot/EpicLoot.cs
+++ b/EpicLoot/EpicLoot.cs
@@ -38,7 +38,7 @@ namespace EpicLoot;
public sealed class EpicLoot : BaseUnityPlugin {
public const string PluginId = "randyknapp.mods.epicloot";
public const string DisplayName = "Epic Loot";
- public const string Version = "0.14.9";
+ public const string Version = "0.14.13";
private static string ConfigFileName = PluginId + ".cfg";
private static string ConfigFileFullPath = BepInEx.Paths.ConfigPath + Path.DirectorySeparatorChar + ConfigFileName;
@@ -154,6 +154,12 @@ private static void RegisterMagicEffectEvents() {
MagicItemEffects.Shards.LuckyLoot.RegisterDisplayValues();
MagicItemEffects.Shards.Bloodrage.RegisterDisplayValues();
MagicItemEffects.Shards.MeteorSummoner.RegisterDisplayValues();
+ MagicItemEffects.Shards.EikthyrShockingCharge.RegisterDisplayValues();
+ MagicItemEffects.Shards.ElderForestsAid.RegisterDisplayValues();
+ MagicItemEffects.Shards.BonemassCorpseRot.RegisterDisplayValues();
+ MagicItemEffects.Shards.ModerIcyRetribution.RegisterDisplayValues();
+ MagicItemEffects.Shards.QueenEverflow.RegisterDisplayValues();
+ MagicItemEffects.Shards.NecroticFire.RegisterDisplayValues();
MagicItemEffects.Shards.BlockAsDodgeAsBlock.RegisterDisplayValues();
MagicItemEffects.Shards.BlockAsWoodCuttingAndPickaxes.RegisterDisplayValues();
diff --git a/EpicLoot/Package/CHANGELOG.md b/EpicLoot/Package/CHANGELOG.md
index 6f0b5ff3f..6b0df27d3 100644
--- a/EpicLoot/Package/CHANGELOG.md
+++ b/EpicLoot/Package/CHANGELOG.md
@@ -1,3 +1,82 @@
+**0.14.14**
+* Fix for bounty creatures losing their extra health once they were loaded again or another player's game took them over
+ * With StarLevelSystem the bonus is now kept on the creature, and needs StarLevelSystem 1.18.2 to apply on every player's game
+ * Without StarLevelSystem, a bounty creature loaded at full health no longer drops back to its normal health
+* Bounty targets now scale as StarLevelSystem bosses from the moment they spawn, not only after a reload
+* StarLevelSystem no longer deletes, copies or re-levels bounty targets and their minions (needs StarLevelSystem 1.18.2)
+
+**0.14.13**
+* Adds a repair system for bounties/treasure which had not previous spawned, due to 1.0 save migration
+* Fix for rune etching being free
+ * The rune tab's effect list is now locked during the countdown
+* Tempering Fixes
+ * Fix for tempering an item without paying for it
+ * Fix for effects on unique legendaries and mythics not being temperable at all
+ * A failed temper no longer raises an effect that was below its rarity's minimum up to the minimum
+* Fix for identifying items consuming them without giving anything back
+* Fix for identifying several items at once handing back more items than were identified
+* The identify preview only lists items progression gating will actually hand out
+* In the "Player Must Have Crafted Item" drop mode, identifying no longer caps items at the biome of the last boss killed
+* Fix for extracting the last enchantment from a socketed item destroying its sockets and the shardstones in them
+* Extracting an enchantment no longer resets the item's augment history (which reset the rising re-augment cost), and augmented/tempered markers stay on the effects that earned them
+* Sacrifice, identify, rune etching and rune extraction only pay out for items they actually removed (no cost when cancelled, no gain either)
+* Unknown magic effects (a typo in legendaries.json, a removed mod) are skipped instead of being given made-up value ranges, and the missing-definition warning is logged once
+* Hold-Shift ranges, temper ranges and the augment and rune lists now show the correct range for effects on unique items
+* Blood Block's self-damage no longer triggers any effect
+* Forest's Aid and Icy Retribution now need an attacker; falls, lava and drowning no longer trigger them
+* Damage dealt by effects themselves is no longer treated as a weapon strike
+ * Reflect, Eikthyr's charge, Frost AOE, Trailblazer, the meteor, Icy Retribution, Corpse Rot and chain lightning no longer crit, double, pay Wager or Mercenary per target, lifesteal, execute, Slow, Paralyze or build Eikthyr charges
+* Fix for Reflect and other effects reading a creature's attack as your weapon whenever your game controlled the attacking creature
+* Arrows and thrown weapons now use the effects of the weapon that fired them
+* Executioner is only used up by a hit that carries Executioner
+* Fix for Wager refunding its stake on hits that did not kill (single player and hosts); Stamina on Kill and Queen's Everflow used the same check
+* Fix for Triple Shot and Double Magic Shot able to leave every weapon of that kind at reduced damage until restart, if an error happened mid-shot
+* Fix for Explosive Arrows' explosion hurting players without PvP enabled
+* The parry bonus in shield tooltips now shows the real multiplier
+* Fix for Immovable blocks failing silently once the stagger bar filled
+* Fix for blocked hits being converted to elemental damage twice
+* Eitr Shield and Coinplated now only pay for damage that gets past the Staff of Protection bubble, blocking, resistances and armor
+* Automatic Mead now judges the damage that will actually land, drinks at most one mead per hit, and works with Instant Mead
+* Skill enchantments now count wherever the game reads the skill level
+* Skill enchantments now add weapon damage past skill 100
+
+
+**0.14.12**
+* Fixes a bug with the new save system which would cause Bounties to get lost
+
+**0.14.11**
+* Gamepad support for the enchanting table (Thanks ksavila!)
+* Gamepad support for the merchant panel (Thanks ksavila!)
+* The Compendium's magic pages now open when navigated to with a gamepad, and the right stick scrolls them
+* The bounty and treasure map pin filters moved into the vanilla map's pin filter panel, beside the boss and death filters
+ * They toggle the same way the vanilla filters do and can be reached with the d-pad, so they no longer take the gamepad triggers
+ * Selecting one no longer lets you place your own map pins with the bounty or treasure icon
+* Fix for cancelling a sacrifice or identify not stopping it
+ * The countdown kept running after the cancel, so the items were consumed anyway
+* Fix for the Rune tab's action button reading "Apply Rune" after every finished action, even in Extract mode
+* Deep North trophies can now be sacrificed
+* Items auto-added from other mods now recognize the Deep North materials, so gear crafted from them is gated behind the Biome boss
+* Dark Green shardstones on melee weapons now imbue 4/5/6/7/8/9% poison damage (Magic to Ancient), down from 10/20/30/40/50/60%
+* Translations updated for all 34 non-English languages
+
+**0.14.10**
+* Fix for items losing their enchantment when taken off an armor stand
+ * The enchantment data was still on the item, but nothing re-read it, so the item behaved as if it were mundane until it was dropped on the ground again
+* Fix for the trader window not opening at all on some clients
+ * Vanilla closes the store on the same Use press that opened it whenever the game happens to run Player's update before StoreGui's, so the window was created and destroyed in one frame, drawing nothing and logging nothing
+ * The store now ignores that one press, the same way the inventory window already does
+* Fixes for the bounty ledger which could reset when the server was restarted
+ * This could result in bounties being marked incomplete after being completed (but not turned in before a server restart)
+ * This could also result in bounties not being properly marked as completed if the owner was offline during the kill, and before a server restart
+* Fixes for not persisting bounty or treasuremap data to the character
+* Fix for magic effect values being permanently reset to 1 when an item was reloaded
+ * An effect was treated as valueless (a yes/no grant like Indestructible) whenever the loaded magiceffects.json had no value block for that item's rarity, and the reset was written straight back to the item
+ * This hit Ancient items on servers whose magiceffects.json predates the Ancient rarity, since a server replaces the client's enchantment definitions on connect
+ * Only effects with no values at any rarity are normalized now; a missing rarity block logs a warning naming the effect and rarity instead of destroying the rolled value
+* Boss shardstones now show what their upgrade does
+ * Shocking Charge, Forest's Aid, Corpse Rot, Icy Retribution, Queen's Everflow and Necrotic Fire had no number in their tooltip at all, so every rarity read identically
+ * All 35 languages carry the new numbersw
+
**0.14.9**
* Fix for Grappling hook
* Trader window diagnostics, please report your log if you have issues opening the trader window
diff --git a/EpicLoot/Package/manifest.json b/EpicLoot/Package/manifest.json
index 31827b398..9937cb544 100644
--- a/EpicLoot/Package/manifest.json
+++ b/EpicLoot/Package/manifest.json
@@ -1,6 +1,6 @@
{
"name": "EpicLoot",
- "version_number": "0.14.9",
+ "version_number": "0.14.13",
"website_url": "https://discord.gg/ZNhYeavv3C",
"description": "Adds loot drops, magic items, and enchanting to Valheim.",
"dependencies": [
diff --git a/EpicLoot/config/enchantcosts.json b/EpicLoot/config/enchantcosts.json
index 28fe36500..e7a8f99cf 100755
--- a/EpicLoot/config/enchantcosts.json
+++ b/EpicLoot/config/enchantcosts.json
@@ -405,7 +405,9 @@
"$item_trophy_surtling",
"$item_trophy_wolf",
"$item_trophy_wraith",
- "$item_trophy_bjorn"
+ "$item_trophy_bjorn",
+ "$item_trophy_deer_white",
+ "$item_trophy_blob_frost"
],
"Products": [
{
@@ -431,7 +433,8 @@
"$item_trophy_ulv",
"$item_trophy_growth",
"$item_trophy_tick",
- "$item_trophy_hare"
+ "$item_trophy_hare",
+ "$item_trophy_writhan"
],
"Products": [
{
@@ -476,7 +479,8 @@
"$item_trophy_charredmage",
"$item_trophy_charredmelee",
"$item_trophy_fallenvalkyrie",
- "$item_trophy_morgen"
+ "$item_trophy_morgen",
+ "$item_trophy_blob_lava"
],
"Products": [
{
@@ -485,6 +489,27 @@
}
]
},
+ {
+ "ItemTypes": [
+ "Trophy"
+ ],
+ "ItemNames": [
+ "$item_trophy_seal",
+ "$item_trophy_moose",
+ "$item_trophy_elaking",
+ "$item_trophy_mole",
+ "$item_trophy_blob_morkhalla",
+ "$item_trophy_jotunwarrior",
+ "$item_trophy_jotunwitch",
+ "$item_trophy_barka"
+ ],
+ "Products": [
+ {
+ "Item": "ShardAncient",
+ "Amount": 1
+ }
+ ]
+ },
{
"ItemTypes": [
"Trophy"
@@ -583,6 +608,17 @@
}
]
},
+ {
+ "ItemNames": [
+ "$item_frozenking_drop"
+ ],
+ "Products": [
+ {
+ "Item": "RunestoneAncient",
+ "Amount": 1
+ }
+ ]
+ },
{
"ItemNames": [
"$item_cryptkey"
diff --git a/EpicLoot/config/itemsorter.json b/EpicLoot/config/itemsorter.json
index bbbb885b2..4e0a29b53 100644
--- a/EpicLoot/config/itemsorter.json
+++ b/EpicLoot/config/itemsorter.json
@@ -85,7 +85,8 @@
"Guck",
"Chitin",
"SerpentScale",
- "Ooze"
+ "Ooze",
+ "WrithanRoots"
],
"BiomeSpecificCraftingStations": []
},
@@ -160,6 +161,7 @@
"FrozenFuel",
"MooseHide",
"MooseSinew",
+ "MooseMeat",
"SealHide",
"SealBlubber",
"ElakingHairBundle",
@@ -168,7 +170,6 @@
"MoleClaws",
"BarkaBranch",
"OozeMork",
- "WrithanRoots",
"OrbFrostFire",
"OrbThunderBlood",
"AncientCoin",
@@ -177,8 +178,58 @@
"AncientGemstoneOrange",
"AncientGemstonePurple",
"MemorialCoal",
+ "HatefulBlood",
+ "BloodGoldKey",
"CrownJewel",
- "FrozenKingDrop"
+ "FrozenKingDrop",
+ "Upgrader7Armor",
+ "Upgrader7Weapon",
+ "Lingonberry",
+ "GlowWorm",
+ "Kale",
+ "KaleSeeds",
+ "Oat",
+ "OatSeeds",
+ "Poteitr",
+ "PoteitrSeeds",
+ "FirConeFrost",
+ "TrophySeal",
+ "TrophyMoose",
+ "TrophyElaking",
+ "TrophyMole",
+ "TrophyBlob_Morkhalla",
+ "TrophyJotunWarrior",
+ "TrophyJotunWitch",
+ "TrophyBarka",
+ "MoldArmorGoldChest",
+ "MoldArmorGoldHelmet",
+ "MoldArmorGoldLegs",
+ "MoldArmormediumChest",
+ "MoldArmorMediumHelmet",
+ "MoldArmorMediumLegs",
+ "MoldArmorMageChest",
+ "MoldArmorMageHelmet",
+ "MoldArmorMageLegs",
+ "MoldAtgeir",
+ "MoldAxe",
+ "MoldAxe2H",
+ "MoldBow",
+ "MoldCrossbow",
+ "MoldFistweapon",
+ "MoldKeys",
+ "MoldKnife",
+ "MoldMace",
+ "MoldMace2H",
+ "MoldShieldBuckler",
+ "MoldShieldRound",
+ "MoldShieldTower",
+ "MoldSpear",
+ "MoldStaffOrbofAhri",
+ "MoldStafffrostorbs",
+ "MoldStaffspiritcaller",
+ "MoldStaffthunderblood",
+ "MoldSword",
+ "MoldSword2H"
],
"BiomeSpecificCraftingStations": []
}
diff --git a/EpicLoot/config/shardstones.json b/EpicLoot/config/shardstones.json
index 7053f4331..406084b2b 100644
--- a/EpicLoot/config/shardstones.json
+++ b/EpicLoot/config/shardstones.json
@@ -1410,12 +1410,12 @@
"MeleeWeapon": {
"EffectType": "AddPoisonDamage",
"ValuesPerRarity": {
- "Magic": 10,
- "Rare": 20,
- "Epic": 30,
- "Legendary": 40,
- "Mythic": 50,
- "Ancient": 60
+ "Magic": 4,
+ "Rare": 5,
+ "Epic": 6,
+ "Legendary": 7,
+ "Mythic": 8,
+ "Ancient": 9
}
},
"RangedWeapon": {
@@ -2405,7 +2405,9 @@
"Ancient": 30
},
"Config": {
- "MaxCharges": 15,
+ "MaxCharges": 18,
+ "ChargesPerValue": 0.3,
+ "MinCharges": 6,
"DamageFraction": 0.3,
"ConeLength": 4,
"ConeMaxWidth": 4,
@@ -2434,8 +2436,9 @@
"Ancient": 30
},
"Config": {
- "Cooldown": 30,
+ "Cooldown": 60,
"CooldownPerValue": 1.5,
+ "MinCooldown": 10,
"BaseRadius": 6,
"RadiusPerTier": 0.15
}
@@ -2487,8 +2490,9 @@
"Ancient": 30
},
"Config": {
- "BaseCooldown": 140,
+ "BaseCooldown": 200,
"CooldownPerRarity": 20,
+ "MinCooldown": 30,
"Radius": 8,
"FrostPerTier": 8
}
diff --git a/EpicLoot/external/SLSAPI.cs b/EpicLoot/external/SLSAPI.cs
index d73d984b6..fe4892dea 100644
--- a/EpicLoot/external/SLSAPI.cs
+++ b/EpicLoot/external/SLSAPI.cs
@@ -6,7 +6,7 @@
namespace StarLevelSystem
{
-
+
[PublicAPI]
public static class API
{
@@ -35,6 +35,7 @@ public static class API
private static readonly MethodInfo SetAllDamageBonus;
private static readonly MethodInfo ApplyUpdatesToCreature;
+ private static readonly MethodInfo SetCreatureSpawnManagedMethod;
private static readonly MethodInfo GetPossibleModifiersForType;
private static readonly MethodInfo GetAllModifiersForCreature;
@@ -42,22 +43,53 @@ public static class API
private static readonly MethodInfo AddNewModifierToSLS;
+ // Location Reset
+ private static readonly MethodInfo RegisterLocationResetTargetMethod;
+ private static readonly MethodInfo UnregisterLocationResetTargetMethod;
+ private static readonly MethodInfo GetRegisteredLocationResetTargetsMethod;
+ private static readonly MethodInfo GetLocationResetTargetInfoMethod;
+ private static readonly MethodInfo IsLocationResetReadyMethod;
+ private static readonly MethodInfo GetLocationResetStatusMethod;
+ private static readonly MethodInfo IsKnownResetTargetNameMethod;
+ private static readonly MethodInfo GetLocationLastResetMethod;
+ private static readonly MethodInfo GetSecondsUntilLocationResetMethod;
+ private static readonly MethodInfo GetLocationResetInfoMethod;
+ private static readonly MethodInfo GetChunkResetInfoMethod;
+ private static readonly MethodInfo ResetNamedLocationMethod;
+ private static readonly MethodInfo ResetLocationsInRadiusMethod;
+
+ ///
+ /// True when Star Level System is installed at all. This only proves the receiver TYPE
+ /// resolved - it says nothing about which methods that build actually has.
+ ///
public static bool IsAvailable => APIReciever != null;
- static API()
- {
+ ///
+ /// True when the installed Star Level System is new enough to expose the Location Reset API.
+ /// Check this rather than IsAvailable before calling anything in that section: a consumer
+ /// shipping a newer copy of this file than the installed mod would otherwise call through a
+ /// null MethodInfo.
+ ///
+ public static bool SupportsLocationReset => ResetNamedLocationMethod != null;
+
+ ///
+ /// True when the installed Star Level System has SetCreatureSpawnManaged. Older builds make that call return false.
+ ///
+ public static bool SupportsSpawnManaged => SetCreatureSpawnManagedMethod != null;
+
+ static API() {
APIReciever = Type.GetType("StarLevelSystem.modules.APIReciever, StarLevelSystem");
if (APIReciever == null) return;
UpdateCreatureLevel = APIReciever.GetMethod("UpdateCreatureLevel", BindingFlags.Public | BindingFlags.Static);
- UpdateCreatureColorization = APIReciever.GetMethod("UpdateCreatureColorization", BindingFlags.Public | BindingFlags.Static);
- GetBaseAttributeValue = APIReciever.GetMethod("GetBaseAttributeValue", BindingFlags.Public | BindingFlags.Static);
- UpdateCreatureBaseAttributes = APIReciever.GetMethod("UpdateCreatureBaseAttributes", BindingFlags.Public | BindingFlags.Static);
- GetAllBaseAttributeValues = APIReciever.GetMethod("GetAllBaseAttributeValues", BindingFlags.Public | BindingFlags.Static);
- SetAllBaseAttributeValues = APIReciever.GetMethod("SetAllBaseAttributeValues", BindingFlags.Public | BindingFlags.Static);
+ UpdateCreatureColorization = APIReciever.GetMethod("UpdateCreatureColorization", BindingFlags.Public | BindingFlags.Static);
+ GetBaseAttributeValue = APIReciever.GetMethod("GetBaseAttributeValue", BindingFlags.Public | BindingFlags.Static);
+ UpdateCreatureBaseAttributes = APIReciever.GetMethod("UpdateCreatureBaseAttributes", BindingFlags.Public | BindingFlags.Static);
+ GetAllBaseAttributeValues = APIReciever.GetMethod("GetAllBaseAttributes", BindingFlags.Public | BindingFlags.Static);
+ SetAllBaseAttributeValues = APIReciever.GetMethod("SetAllBaseAttributes", BindingFlags.Public | BindingFlags.Static);
GetPerLevelAttributeValue = APIReciever.GetMethod("GetPerLevelAttributeValue", BindingFlags.Public | BindingFlags.Static);
UpdateCreaturePerLevelAttributes = APIReciever.GetMethod("UpdateCreaturePerLevelAttributes", BindingFlags.Public | BindingFlags.Static);
- GetAllPerLevelAttributeValues = APIReciever.GetMethod("GetAllPerLevelAttributeValues", BindingFlags.Public | BindingFlags.Static);
- SetAllPerLevelAttributeValues = APIReciever.GetMethod("SetAllPerLevelAttributeValues", BindingFlags.Public | BindingFlags.Static);
+ GetAllPerLevelAttributeValues = APIReciever.GetMethod("GetAllPerLevelAttributes", BindingFlags.Public | BindingFlags.Static);
+ SetAllPerLevelAttributeValues = APIReciever.GetMethod("SetAllPerLevelAttributes", BindingFlags.Public | BindingFlags.Static);
GetCreatureDamageRecievedModifier = APIReciever.GetMethod("GetCreatureDamageRecievedModifier", BindingFlags.Public | BindingFlags.Static);
UpdateCreatureDamageRecievedModifier = APIReciever.GetMethod("UpdateCreatureDamageRecievedModifier", BindingFlags.Public | BindingFlags.Static);
GetAllDamageRecievedModifiers = APIReciever.GetMethod("GetAllDamageRecievedModifiers", BindingFlags.Public | BindingFlags.Static);
@@ -67,10 +99,36 @@ static API()
GetAllDamageBonus = APIReciever.GetMethod("GetAllDamageBonus", BindingFlags.Public | BindingFlags.Static);
SetAllDamageBonus = APIReciever.GetMethod("SetAllDamageBonus", BindingFlags.Public | BindingFlags.Static);
ApplyUpdatesToCreature = APIReciever.GetMethod("ApplyUpdatesToCreature", BindingFlags.Public | BindingFlags.Static);
+ SetCreatureSpawnManagedMethod = APIReciever.GetMethod("SetCreatureSpawnManaged", BindingFlags.Public | BindingFlags.Static);
GetPossibleModifiersForType = APIReciever.GetMethod("GetPossibleModifiersForType", BindingFlags.Public | BindingFlags.Static);
GetAllModifiersForCreature = APIReciever.GetMethod("GetAllModifiersForCreature", BindingFlags.Public | BindingFlags.Static);
AddModifierToCreature = APIReciever.GetMethod("AddModifierToCreature", BindingFlags.Public | BindingFlags.Static);
AddNewModifierToSLS = APIReciever.GetMethod("AddNewModifierToSLS", BindingFlags.Public | BindingFlags.Static);
+
+ RegisterLocationResetTargetMethod = APIReciever.GetMethod("RegisterLocationResetTarget", BindingFlags.Public | BindingFlags.Static);
+ UnregisterLocationResetTargetMethod = APIReciever.GetMethod("UnregisterLocationResetTarget", BindingFlags.Public | BindingFlags.Static);
+ GetRegisteredLocationResetTargetsMethod = APIReciever.GetMethod("GetRegisteredLocationResetTargets", BindingFlags.Public | BindingFlags.Static);
+ GetLocationResetTargetInfoMethod = APIReciever.GetMethod("GetLocationResetTargetInfo", BindingFlags.Public | BindingFlags.Static);
+ IsLocationResetReadyMethod = APIReciever.GetMethod("IsLocationResetReady", BindingFlags.Public | BindingFlags.Static);
+ GetLocationResetStatusMethod = APIReciever.GetMethod("GetLocationResetStatus", BindingFlags.Public | BindingFlags.Static);
+ IsKnownResetTargetNameMethod = APIReciever.GetMethod("IsKnownResetTargetName", BindingFlags.Public | BindingFlags.Static);
+ GetLocationLastResetMethod = APIReciever.GetMethod("GetLocationLastReset", BindingFlags.Public | BindingFlags.Static);
+ GetSecondsUntilLocationResetMethod = APIReciever.GetMethod("GetSecondsUntilLocationReset", BindingFlags.Public | BindingFlags.Static);
+ GetLocationResetInfoMethod = APIReciever.GetMethod("GetLocationResetInfo", BindingFlags.Public | BindingFlags.Static);
+ GetChunkResetInfoMethod = APIReciever.GetMethod("GetChunkResetInfo", BindingFlags.Public | BindingFlags.Static);
+ ResetNamedLocationMethod = APIReciever.GetMethod("ResetNamedLocation", BindingFlags.Public | BindingFlags.Static);
+ ResetLocationsInRadiusMethod = APIReciever.GetMethod("ResetLocationsInRadius", BindingFlags.Public | BindingFlags.Static);
+ }
+
+ // Invoke that degrades instead of throwing.
+ //
+ // The creature methods above call Invoke straight off the field, so a consumer shipping a
+ // NEWER copy of this file than the installed Star Level System gets a NullReferenceException
+ // at their own call site rather than a usable answer. Everything added from the Location
+ // Reset section onward routes through here and returns `fallback` instead.
+ private static object Call(MethodInfo method, object fallback, params object[] args) {
+ if (method == null) { return fallback; }
+ return method.Invoke(null, args);
}
/////////////////////
@@ -84,8 +142,7 @@ static API()
/// The creature's Character class
/// The new level to set the creature to
/// returns>bool success
- public static bool SetCreatureLevel(Character creatureId, int newLevel)
- {
+ public static bool SetCreatureLevel(Character creatureId, int newLevel) {
return (bool)UpdateCreatureLevel.Invoke(null, new object[] { creatureId, newLevel });
}
@@ -103,8 +160,7 @@ public static bool SetCreatureLevel(Character creatureId, int newLevel)
/// unity material saturation
/// enables emission on the material, if enabled, the emissive color will be value, hue, saturation
/// returns>bool success
- public static bool SetCreatureColorization(Character creatureId, float value, float hue, float sat, bool emission = false)
- {
+ public static bool SetCreatureColorization(Character creatureId, float value, float hue, float sat, bool emission = false) {
return (bool)UpdateCreatureColorization.Invoke(null, new object[] { creatureId, value, hue, sat, emission });
}
@@ -118,8 +174,7 @@ public static bool SetCreatureColorization(Character creatureId, float value, fl
/// The creature's Character class
/// The enum value of which attribute to get: BaseHealth = 0, BaseDamage = 1, AttackSpeed = 2, Speed = 3, Size = 4
/// returns>float value of the base attribute
- public static float GetCreatureBaseAttribute(Character creatureId, int attribute)
- {
+ public static float GetCreatureBaseAttribute(Character creatureId, int attribute) {
return (float)GetBaseAttributeValue.Invoke(null, new object[] { creatureId, attribute });
}
@@ -127,12 +182,16 @@ public static float GetCreatureBaseAttribute(Character creatureId, int attribute
/// This allows setting modifiers to any of a creatures base attributes (this value is applied once, flat addition)
/// this does not apply immediately and must be applied with ApplyCreatureUpdates
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ ///
/// The creature's Character class
/// The enum value of which attribute to get: BaseHealth = 0, BaseDamage = 1, AttackSpeed = 2, Speed = 3, Size = 4
/// The value this attribute will be set to (overrides existing)
/// returns>bool success
- public static bool SetCreatureBaseAttribute(Character creatureId, int attribute, float value)
- {
+ public static bool SetCreatureBaseAttribute(Character creatureId, int attribute, float value) {
return (bool)UpdateCreatureBaseAttributes.Invoke(null, new object[] { creatureId, attribute, value });
}
@@ -142,8 +201,7 @@ public static bool SetCreatureBaseAttribute(Character creatureId, int attribute,
///
/// The creature's Character class
/// returns>Dictionary
- public static Dictionary GetAllCreatureBaseAttributes(Character creatureId)
- {
+ public static Dictionary GetAllCreatureBaseAttributes(Character creatureId) {
return (Dictionary)GetAllBaseAttributeValues.Invoke(null, new object[] { creatureId });
}
@@ -152,11 +210,15 @@ public static Dictionary GetAllCreatureBaseAttributes(Character crea
/// and sets their values for the creature
/// this applies immediately
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ ///
/// The creature's Character class
/// Dictionary of all creatures attributes
/// returns>bool success
- public static bool SetAllCreatureBaseAttributes(Character creatureId, Dictionary attributes)
- {
+ public static bool SetAllCreatureBaseAttributes(Character creatureId, Dictionary attributes) {
return (bool)SetAllBaseAttributeValues.Invoke(null, new object[] { creatureId, attributes });
}
@@ -170,8 +232,7 @@ public static bool SetAllCreatureBaseAttributes(Character creatureId, Dictionary
/// The creature's Character class
/// The enum value of which attribute to get: HealthPerLevel = 0, DamagePerLevel = 1, SpeedPerLevel = 2, AttackSpeedPerLevel = 3, SizePerLevel = 4
/// returns>float value of the per level attribute
- public static float GetCreaturePerLevelAttribute(Character creatureId, int attribute)
- {
+ public static float GetCreaturePerLevelAttribute(Character creatureId, int attribute) {
return (float)GetPerLevelAttributeValue.Invoke(null, new object[] { creatureId, attribute });
}
@@ -179,12 +240,16 @@ public static float GetCreaturePerLevelAttribute(Character creatureId, int attri
/// This allows setting modifiers to any of a creatures per level attributes (this value is applied once for every level)
/// this does not apply immediately and must be applied with ApplyCreatureUpdates
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ ///
/// The creature's Character class
/// The enum value of which attribute to get: HealthPerLevel = 0, DamagePerLevel = 1, SpeedPerLevel = 2, AttackSpeedPerLevel = 3, SizePerLevel = 4
/// The value this attribute will be set to (overrides existing)
/// returns>bool success
- public static bool SetCreaturePerLevelAttribute(Character creatureId, int attribute, float value)
- {
+ public static bool SetCreaturePerLevelAttribute(Character creatureId, int attribute, float value) {
return (bool)UpdateCreaturePerLevelAttributes.Invoke(null, new object[] { creatureId, attribute, value });
}
@@ -194,8 +259,7 @@ public static bool SetCreaturePerLevelAttribute(Character creatureId, int attrib
///
/// The creature's Character class
/// returns>Dictionary
- public static Dictionary GetAllCreaturePerLevelAttributes(Character creatureId)
- {
+ public static Dictionary GetAllCreaturePerLevelAttributes(Character creatureId) {
return (Dictionary)GetAllPerLevelAttributeValues.Invoke(null, new object[] { creatureId });
}
@@ -204,11 +268,15 @@ public static Dictionary GetAllCreaturePerLevelAttributes(Character
/// and sets their values for the creature
/// this applies immediately
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ ///
/// The creature's Character class
/// Dictionary of all creatures attributes
/// returns>bool success
- public static bool SetAllCreaturePerLevelAttributes(Character creatureId, Dictionary attributes)
- {
+ public static bool SetAllCreaturePerLevelAttributes(Character creatureId, Dictionary attributes) {
return (bool)SetAllPerLevelAttributeValues.Invoke(null, new object[] { creatureId, attributes });
}
@@ -223,8 +291,7 @@ public static bool SetAllCreaturePerLevelAttributes(Character creatureId, Dictio
/// The creature's Character class
/// The enum value of which attribute to get: Blunt = 0, Slash = 1, Pierce = 2, Fire = 3, Frost = 4, Lightning = 5, Poison = 6, Spirit = 7, Chop = 8, Pickaxe = 9
/// returns>float value damage recieved modifier
- public static float GetCreatureDamageReceivedModifier(Character creatureId, int damageType)
- {
+ public static float GetCreatureDamageReceivedModifier(Character creatureId, int damageType) {
return (float)GetCreatureDamageRecievedModifier.Invoke(null, new object[] { creatureId, damageType });
}
@@ -233,12 +300,17 @@ public static float GetCreatureDamageReceivedModifier(Character creatureId, int
/// 1.0 = 100% damage taken, 0.5 = 50% damage taken, 2.0 = 200% damage taken
/// this does not apply immediately and must be applied with ApplyCreatureUpdates
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ /// After a rebuild the persisted value is the base the creature's own modifiers (Resist*, Flame, ...) stack on.
+ ///
/// The creature's Character class
/// The enum value of which attribute to get: Blunt = 0, Slash = 1, Pierce = 2, Fire = 3, Frost = 4, Lightning = 5, Poison = 6, Spirit = 7, Chop = 8, Pickaxe = 9
/// The value this attribute will be set to (overrides existing)
/// returns>bool success
- public static bool SetCreatureDamageReceivedModifier(Character creatureId, int damageType, float value)
- {
+ public static bool SetCreatureDamageReceivedModifier(Character creatureId, int damageType, float value) {
return (bool)UpdateCreatureDamageRecievedModifier.Invoke(null, new object[] { creatureId, damageType, value });
}
@@ -248,8 +320,7 @@ public static bool SetCreatureDamageReceivedModifier(Character creatureId, int d
///
/// The creature's Character class
/// returns>Dictionary
- public static Dictionary GetAllCreatureDamageReceivedModifiers(Character creatureId)
- {
+ public static Dictionary GetAllCreatureDamageReceivedModifiers(Character creatureId) {
return (Dictionary)GetAllDamageRecievedModifiers.Invoke(null, new object[] { creatureId });
}
@@ -257,10 +328,15 @@ public static Dictionary GetAllCreatureDamageReceivedModifiers(Chara
/// Sets all of the creature damage recieved modifiers as a dictionary with the key as the enum (Blunt = 0, Slash = 1, Pierce = 2, Fire = 3, Frost = 4, Lightning = 5, Poison = 6, Spirit = 7, Chop = 8, Pickaxe = 9)
/// 1.0 = 100% damage taken, 0.5 = 50% damage taken, 2.0 = 200% damage taken
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ /// After a rebuild the persisted value is the base the creature's own modifiers (Resist*, Flame, ...) stack on.
+ ///
/// The creature's Character class
/// Dictionary of creatures damage recived modifiers
- public static bool SetAllCreatureDamageReceivedModifiers(Character creatureId, Dictionary attributes)
- {
+ public static bool SetAllCreatureDamageReceivedModifiers(Character creatureId, Dictionary attributes) {
return (bool)SetAllDamageRecievedModifiers.Invoke(null, new object[] { creatureId, attributes });
}
@@ -274,8 +350,7 @@ public static bool SetAllCreatureDamageReceivedModifiers(Character creatureId, D
/// The creature's Character class
/// The enum value of which attribute to get: Blunt = 0, Slash = 1, Pierce = 2, Fire = 3, Frost = 4, Lightning = 5, Poison = 6, Spirit = 7, Chop = 8, Pickaxe = 9
/// returns>float value of the base attribute
- public static float GetCreatureFlatDamageBonus(Character creatureId, int damageType)
- {
+ public static float GetCreatureFlatDamageBonus(Character creatureId, int damageType) {
return (float)GetCreatureDamageBonus.Invoke(null, new object[] { creatureId, damageType });
}
@@ -283,12 +358,17 @@ public static float GetCreatureFlatDamageBonus(Character creatureId, int damageT
/// Allows setting flat damage bonus values for a creature (this value is applied once, flat addition)
/// this does not apply immediately and must be applied with ApplyCreatureUpdates
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ /// After a rebuild the persisted value is the base the creature's own modifiers (Resist*, Flame, ...) stack on.
+ ///
/// The creature's Character class
/// The enum value of which attribute to get: Blunt = 0, Slash = 1, Pierce = 2, Fire = 3, Frost = 4, Lightning = 5, Poison = 6, Spirit = 7, Chop = 8, Pickaxe = 9
/// The value this attribute will be set to (overrides existing)
/// returns>bool success
- public static bool SetCreatureFlatDamageBonus(Character creatureId, int damageType, float value)
- {
+ public static bool SetCreatureFlatDamageBonus(Character creatureId, int damageType, float value) {
return (bool)UpdateCreatureDamageBonus.Invoke(null, new object[] { creatureId, damageType, value });
}
@@ -297,8 +377,7 @@ public static bool SetCreatureFlatDamageBonus(Character creatureId, int damageTy
///
/// The creature's Character class
/// returns>Dictionary
- public static Dictionary GetAllCreatureFlatDamageBonuses(Character creatureId)
- {
+ public static Dictionary GetAllCreatureFlatDamageBonuses(Character creatureId) {
return (Dictionary)GetAllDamageBonus.Invoke(null, new object[] { creatureId });
}
@@ -306,11 +385,16 @@ public static Dictionary GetAllCreatureFlatDamageBonuses(Character c
/// Sets all of the creatures flat damage bonuses as a dictionary with the key as the enum (Blunt = 0, Slash = 1, Pierce = 2, Fire = 3, Frost = 4, Lightning = 5, Poison = 6, Spirit = 7, Chop = 8, Pickaxe = 9)
/// this applies immediately
///
+ ///
+ /// Persisted on the creature when called by its ZDO owner, so the value survives a reload, an ownership
+ /// handoff and a Star Level System config reload, and every other peer sees it. A call from a peer that does
+ /// not own the creature changes only that peer's view.
+ /// After a rebuild the persisted value is the base the creature's own modifiers (Resist*, Flame, ...) stack on.
+ ///
/// The creature's Character class
/// Dictionary of all creatures flat damage bonuses
/// returns>bool success
- public static bool SetAllCreatureFlatDamageBonuses(Character creatureId, Dictionary attributes)
- {
+ public static bool SetAllCreatureFlatDamageBonuses(Character creatureId, Dictionary attributes) {
return (bool)SetAllDamageBonus.Invoke(null, new object[] { creatureId, attributes });
}
@@ -324,11 +408,29 @@ public static bool SetAllCreatureFlatDamageBonuses(Character creatureId, Diction
///
/// The creature's Character class
/// returns>bool success
- public static bool ApplyCreatureUpdates(Character creatureId)
- {
+ public static bool ApplyCreatureUpdates(Character creatureId) {
return (bool)ApplyUpdatesToCreature.Invoke(null, new object[] { creatureId });
}
+ ////////////////////////////////////////
+ /// SPAWN MANAGEMENT
+ ////////////////////////////////////////
+
+ ///
+ /// Marks a creature your mod spawned and owns the existence and level of (a quest or bounty target, say).
+ /// Star Level System keeps giving it stats, modifiers and colour, but never deletes or multiplies it for
+ /// spawn-rate or disabled-spawn rules, and never rerolls or clamps its level.
+ /// Only the creature's ZDO owner can set this. Call it in the frame you spawn the creature (Star Level System's
+ /// own setup waits InitialDelayBeforeSetup), and again when it loads if it may have been spawned before your
+ /// mod made this call.
+ ///
+ /// The creature's Character class
+ /// True to mark the creature, false to hand it back to Star Level System's spawn rules
+ /// returns>bool success; false when this peer does not own the creature or Star Level System is too old (see SupportsSpawnManaged)
+ public static bool SetCreatureSpawnManaged(Character creatureId, bool managed = true) {
+ return (bool)Call(SetCreatureSpawnManagedMethod, false, creatureId, managed);
+ }
+
////////////////////////////////////////
/// Creature Modifier Management
////////////////////////////////////////
@@ -339,8 +441,7 @@ public static bool ApplyCreatureUpdates(Character creatureId)
///
/// The type of modifier to get (types = 0 = Major, 1 = Minro, 2 = Boss)
/// returns>List of modifier names
- public static List GetPossibleModifiers(int modType = 0)
- {
+ public static List GetPossibleModifiers(int modType = 0) {
return (List)GetPossibleModifiersForType.Invoke(null, new object[] { modType });
}
@@ -350,8 +451,7 @@ public static List GetPossibleModifiers(int modType = 0)
///
/// The creature's Character class
/// returns>Dictionary of modifier names and type int
- public static Dictionary GetCreaturesModifiers(Character creatureId)
- {
+ public static Dictionary GetCreaturesModifiers(Character creatureId) {
return (Dictionary)GetAllModifiersForCreature.Invoke(null, new object[] { creatureId });
}
@@ -364,8 +464,7 @@ public static Dictionary GetCreaturesModifiers(Character creatureId
/// The modifiers type Major (0), Minor (1), and Boss (2)
/// If true applies updates to the creature to rebuild the creatures name and other stats
/// returns>bool success
- public static bool AddModifierToTargetCreature(Character creatureId, string modifierName, int modifierType, bool update = true)
- {
+ public static bool AddModifierToTargetCreature(Character creatureId, string modifierName, int modifierType, bool update = true) {
return (bool)AddModifierToCreature.Invoke(null, new object[] { creatureId, modifierName, modifierType, update });
}
@@ -397,23 +496,251 @@ public static bool AddModifierToTargetCreature(Character creatureId, string modi
public static bool AddNewModifier(
int modifierID,
string modifier_name,
- string setupMethod = null,
+ Delegate setupMethod = null,
float selectionWeight = 10f,
float basepower = 0f,
float perlevelpower = 0f,
Dictionary> biomeConfig = null,
int namingStyle = 2,
- List name_suffixes = null,
- List name_prefixes = null,
+ string name_suffixes = null,
+ string name_prefixes = null,
int visualStyle = 0,
Sprite starIcon = null,
GameObject visualEffect = null,
List allowed_creatures = null,
List unallowed_creatures = null,
- List allowed_biomes = null)
- {
+ List allowed_biomes = null) {
return (bool)AddNewModifierToSLS.Invoke(null, new object[] { modifierID, modifier_name, setupMethod, selectionWeight, basepower, perlevelpower, biomeConfig, namingStyle,
name_suffixes, name_prefixes, visualStyle, starIcon, visualEffect, allowed_creatures, unallowed_creatures, allowed_biomes });
}
+
+ ////////////////////////////////////////
+ /// LOCATION RESET
+ ////////////////////////////////////////
+ ///
+ /// Star Level System can restore looted locations, dungeons, ore, pickables and vegetation on
+ /// a schedule. This section lets your mod register its own targets for that sweep, ask for a
+ /// reset directly, and find out when something was last reset.
+ ///
+ /// ALL OF IT WORKS FROM A CLIENT. The work happens on the server - it owns the world objects -
+ /// but calling from a client relays the request and brings the answer back, so an item that
+ /// resets a dungeon can run its logic on whoever used it.
+ ///
+ /// That is why every method here takes a callback and returns bool rather than returning the
+ /// answer directly: on a client the answer arrives over the network a moment later.
+ ///
+ /// THE CALLBACK ALWAYS FIRES, EXACTLY ONCE - on success, on a deferral, on a refusal, on a
+ /// timeout, and when there is no server to ask. Refusals arrive as a result carrying
+ /// "outcome" = "refused", a human-readable "reason" and a machine-readable "refusalCode", so
+ /// your follow-up logic lives in the callback and nowhere else. The bool return only tells
+ /// you whether the work started; when it is false the reason has already been delivered.
+ ///
+ /// On the server, and for anything refused before it leaves this machine, the callback runs
+ /// before the call returns.
+ ///
+ /// Guard on SupportsLocationReset, not just IsAvailable.
+
+ ///
+ /// Register a location or vegetation prefab as a recurring reset target. It joins Star Level
+ /// System's normal background sweep exactly as a configured target would, timed off the
+ /// location's own world data, and survives config reloads and world reloads.
+ ///
+ /// Safe to call at any point, including before a world is loaded, if you are the server. From
+ /// a client this relays, so it needs a live server connection - register on connect rather
+ /// than in your Awake if your mod is client-side.
+ ///
+ /// The server owner's LocationResetSettings.yaml always wins: if they add an entry for this
+ /// prefab name, their settings replace yours entirely. Protection rules (what player-built
+ /// content blocks a reset) are theirs alone and cannot be set from here.
+ ///
+ /// Location or vegetation prefab name, e.g. "Crypt2"
+ /// Your plugin GUID. Used in logs and to scope unregistration
+ /// Hours between resets. 0 uses the server's default interval. Minimum 0.25
+ /// A 5-field cron expression instead of an interval, e.g. "0 3 * * *". Wins over resetHours
+ /// Whether the target is active
+ /// 0 = Full (clear and rebuild), 1 = TerrainOnly (undo terraforming, leave the location)
+ /// Also revert player terraforming around the location
+ /// Terrain reset radius. 0 uses the location's own exterior radius
+ /// Extra metres of terrain reset beyond that radius
+ /// Whether a dungeon interior may be regenerated. False leaves such locations alone entirely
+ /// Only apply beyond this distance from the world's reset centre. 0 = no limit
+ /// Only apply within this distance. 0 = no limit
+ /// Receives whether the server accepted the registration
+ /// bool - whether the request was dispatched
+ public static bool RegisterLocationReset(string prefabName, string sourceId,
+ float resetHours = 0f, string resetSchedule = null, bool enabled = true, int mode = 0,
+ bool resetTerrain = false, float terrainRadius = 0f, float extraTerrainRadius = 0f,
+ bool resetInterior = true, float minDistance = 0f, float maxDistance = 0f,
+ Action onResult = null) {
+ return (bool)Call(RegisterLocationResetTargetMethod, false,
+ prefabName, sourceId, resetHours, resetSchedule, mode, resetTerrain, terrainRadius,
+ extraTerrainRadius, resetInterior, minDistance, maxDistance, enabled, onResult);
+ }
+
+ ///
+ /// Remove a target you registered. Refused if another mod owns that registration.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool UnregisterLocationReset(string prefabName, string sourceId, Action onResult = null) {
+ return (bool)Call(UnregisterLocationResetTargetMethod, false, prefabName, sourceId, onResult);
+ }
+
+ ///
+ /// Every prefab name registered through this API. Pass your plugin GUID to see only your own,
+ /// or null for all of them.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool GetRegisteredLocationResets(string sourceId, Action> onResult) {
+ return (bool)Call(GetRegisteredLocationResetTargetsMethod, false, sourceId, onResult);
+ }
+
+ ///
+ /// How a reset target actually resolved, whoever registered it. Keys include: name, known,
+ /// configured, enabled, source ("entry", "group", "api", "defaults", "none"), registeredBy,
+ /// hardBlocked, isLocation, isVegetation, resetSeconds, schedule, mode, resetTerrain,
+ /// terrainRadius, extraTerrainRadius, resetInterior.
+ ///
+ /// "source" is the one to check if your registration does not seem to be taking effect:
+ /// anything other than "api" means the server's config is overriding it.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool GetLocationResetTargetInfo(string prefabName, Action> onResult) {
+ return (bool)Call(GetLocationResetTargetInfoMethod, false, prefabName, onResult);
+ }
+
+ ///
+ /// Whether a reset request would currently be accepted by the server: a world is loaded and
+ /// no conflicting reset mod is installed.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool IsLocationResetReady(Action onResult) {
+ return (bool)Call(IsLocationResetReadyMethod, false, onResult);
+ }
+
+ ///
+ /// Location Reset state on the server. Keys include: available, isServer, ready, sweepAllowed,
+ /// masterSwitchEnabled, configEnabled, blockedByModConflict, resetRunning, trackedZones,
+ /// generatedZones, sweepFloorSeconds, apiRegistrations, and the limits applied to client
+ /// requests: clientMaxRadius, clientMaxDistance, clientCooldownSeconds.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool GetLocationResetStatus(Action> onResult) {
+ return (bool)Call(GetLocationResetStatusMethod, false, onResult);
+ }
+
+ ///
+ /// Whether this world has any location, vegetation entry or prefab by that name. Use it to
+ /// catch a prefab name that a game update renamed out from under you.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool IsKnownLocationResetTarget(string prefabName, Action onResult) {
+ return (bool)Call(IsKnownResetTargetNameMethod, false, prefabName, onResult);
+ }
+
+ ///
+ /// When the named location nearest `center` was last reset, as Unix seconds UTC.
+ /// The callback receives -1 if no location of that name is within `radius` (or it has nothing
+ /// to time from), and 0 if it exists but has never been reset.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool GetLocationLastReset(string locationName, Vector3 center, float radius, Action onResult) {
+ return (bool)Call(GetLocationLastResetMethod, false, locationName, center, radius, onResult);
+ }
+
+ ///
+ /// Seconds until the named location is next due for a reset. The callback receives 0 for due
+ /// now, and -1 for unknown - not found, never reset, or nothing has it configured.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool GetSecondsUntilLocationReset(string locationName, Vector3 center, float radius, Action onResult) {
+ return (bool)Call(GetSecondsUntilLocationResetMethod, false, locationName, center, radius, onResult);
+ }
+
+ ///
+ /// Full reset state for the named location nearest `center`. Keys include: found, name,
+ /// zoneX, zoneZ, positionX/Y/Z, distance, configured, enabled, hardBlocked, source,
+ /// groupName, schedule, mode, resetTerrain, resetInterior, hasProxy, lastResetUnix,
+ /// secondsSinceReset, secondsUntilDue, dueNow, rateMultiplier, rateDescription.
+ ///
+ /// Always check "found" first.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool GetLocationResetInfo(string locationName, Vector3 center, float radius,
+ Action> onResult) {
+ return (bool)Call(GetLocationResetInfoMethod, false, locationName, center, radius, onResult);
+ }
+
+ ///
+ /// Reset state for the map chunk containing `position`. Keys include: zoneX, zoneZ, centerX,
+ /// centerZ, biome, generated, loaded, tracked, lastExaminedUnix, secondsSinceExamined,
+ /// deferredUntilUnix, retryAtUnix, retryCount, rateMultiplier, rateDescription,
+ /// protectionBlocked, protectionReason, locationName, locationLastResetUnix,
+ /// locationSecondsUntilDue.
+ ///
+ /// Note that lastExaminedUnix is when the sweep last LOOKED at this chunk, which is not the
+ /// same as anything in it having been reset - for that, read locationLastResetUnix or use
+ /// GetLocationLastReset. When the chunk has been deferred (a player build is blocking it, for
+ /// instance) lastExaminedUnix is 0 and deferredUntilUnix carries the time it comes back up.
+ ///
+ /// Also return a "prefabs" list of per-prefab census rows
+ /// (name, lastResetUnix, baseline, live). This costs a full scan of the chunk
+ /// bool - whether the request was dispatched
+ public static bool GetChunkResetInfo(Vector3 position, bool includePrefabs,
+ Action> onResult) {
+ return (bool)Call(GetChunkResetInfoMethod, false, position, includePrefabs, onResult);
+ }
+
+ ///
+ /// Reset the named location nearest `center`, within `radius`.
+ ///
+ /// Unlike the background sweep this works on a location the server has not configured for
+ /// resets at all - you named it, so it is reset. Locations that can never be reset (the
+ /// starting temple) are still refused.
+ ///
+ /// SAFETY. safety 0 (Safe, the default) waits for players to leave the affected chunks before
+ /// touching anything, and gives up without resetting if they do not. safety 1 (Force) resets
+ /// immediately, working on chunks that are loaded around a player - use this when you want a
+ /// location restored before somebody walks back into it. Player-built structures block a
+ /// reset in BOTH modes and there is no way to override that.
+ ///
+ /// Be careful with Force on a dungeon somebody is inside: interiors are rebuilt from scratch
+ /// and a player standing in one will fall.
+ ///
+ /// CALLING FROM A CLIENT. The server clamps the radius, refuses a position further away than
+ /// its configured limit, and rate-limits how often any one client may ask. Read
+ /// clientMaxRadius / clientMaxDistance / clientCooldownSeconds from GetLocationResetStatus to
+ /// size your requests. Every refusal reaches onComplete with a refusalCode - "too_far",
+ /// "cooldown", "no_such_location", "already_running" and so on - so a caller can retry,
+ /// explain, or refund without guessing why.
+ ///
+ /// Location prefab name, e.g. "Crypt2"
+ /// World position to search around
+ /// Search radius in metres
+ /// 0 = Safe (wait for players), 1 = Force (reset now)
+ /// Reset every match in range rather than just the nearest
+ /// How long Safe waits before giving up. 0 uses the default (300s)
+ /// Include a per-chunk "zones" list in the result
+ /// Called exactly once: with the result summary when the reset
+ /// finishes, or with a refusal carrying outcome/reason/refusalCode if it never ran
+ /// bool - whether the reset started. False means onComplete has already been given
+ /// the reason
+ public static bool ResetNamedLocation(string locationName, Vector3 center, float radius = 128f,
+ int safety = 0, bool resetAllMatches = false, float safeWaitSeconds = 0f,
+ bool includeDetail = false, Action> onComplete = null) {
+ return (bool)Call(ResetNamedLocationMethod, false,
+ locationName, center, radius, safety, resetAllMatches, safeWaitSeconds, includeDetail, onComplete);
+ }
+
+ ///
+ /// Reset everything the server has configured for resets within `radius` of `center`. Same
+ /// safety rules and same client limits as ResetNamedLocation.
+ ///
+ /// bool - whether the request was dispatched
+ public static bool ResetLocationsInRadius(Vector3 center, float radius = 128f, int safety = 0,
+ float safeWaitSeconds = 0f, bool includeDetail = false,
+ Action> onComplete = null) {
+ return (bool)Call(ResetLocationsInRadiusMethod, false,
+ center, radius, safety, safeWaitSeconds, includeDetail, onComplete);
+ }
}
}
diff --git a/EpicLoot/localizations/Bulgarian.json b/EpicLoot/localizations/Bulgarian.json
index 8eae2733d..9fe41554e 100644
--- a/EpicLoot/localizations/Bulgarian.json
+++ b/EpicLoot/localizations/Bulgarian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Кремък",
"mod_epicloot_config_serpentscale": "Люспа от морска змия",
"mod_epicloot_config_triplechance": "Шанс за троен улов",
- "mod_epicloot_config_secondspercent": "Секунди на 1 %",
+ "mod_epicloot_config_secondsperpercent": "Секунди на 1 %",
"mod_epicloot_config_procchance": "Шанс за задействане",
"mod_epicloot_config_bonusrollsmin": "Мин. бонус хвърляния за плячка",
"mod_epicloot_config_bonusrollsmax": "Макс. бонус хвърляния за плячка",
+ "mod_epicloot_config_cooldown": "Презареждане (с)",
+ "mod_epicloot_config_basecooldown": "Базово презареждане (с)",
+ "mod_epicloot_config_cooldownperrarity": "Презареждане за рядкост (с)",
+ "mod_epicloot_config_cooldownpervalue": "Презареждане за стойност (с)",
+ "mod_epicloot_config_buffduration": "Продължителност (с)",
+ "mod_epicloot_config_tickinterval": "Интервал на действие (с)",
+ "mod_epicloot_config_radius": "Радиус (м)",
+ "mod_epicloot_config_baseradius": "Базов радиус (м)",
+ "mod_epicloot_config_radiuspertier": "Радиус за стойност (м)",
+ "mod_epicloot_config_maxcharges": "Удари до зареждане",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Удари до зареждане (базово)",
+ "mod_epicloot_config_chargespervalue": "Спестени удари за стойност",
+ "mod_epicloot_config_mincharges": "Мин. удари до зареждане",
+ "mod_epicloot_config_mincooldown": "Мин. презареждане (с)",
+ "mod_epicloot_config_damagepertrigger": "Щети за задействане",
+ "mod_epicloot_config_eitrpertrigger": "Ейтър за задействане",
+ "mod_epicloot_config_damagemultiplier": "Множител на щетите",
+ "mod_epicloot_config_damagefraction": "Дял от щетите",
+ "mod_epicloot_config_damagepervalue": "Щети за стойност",
+ "mod_epicloot_config_frostpertier": "Мразовити щети за стойност",
+ "mod_epicloot_config_poisonpertier": "Щети от отрова за стойност",
+ "mod_epicloot_config_spawnheight": "Височина на поява (м)",
+ "mod_epicloot_config_spawnradius": "Радиус на поява (м)",
+ "mod_epicloot_config_spawninterval": "Интервал на поява (с)",
+ "mod_epicloot_config_mindistance": "Мин. разстояние на изстрелване (м)",
+ "mod_epicloot_config_maxdistance": "Макс. разстояние на изстрелване (м)",
+ "mod_epicloot_config_projectilespeed": "Скорост на снаряда (м/с)",
+ "mod_epicloot_config_explosionradius": "Радиус на взрива (м)",
+ "mod_epicloot_config_conelength": "Дължина на конуса (м)",
+ "mod_epicloot_config_conemaxwidth": "Ширина на конуса (м)",
+ "mod_epicloot_config_dischargeignorewindow": "Прозорец за пренебрегване на разряда (с)",
+ "mod_epicloot_config_minmovespeed": "Мин. скорост на движение",
+ "mod_epicloot_config_patchradius": "Радиус на петното (м)",
+ "mod_epicloot_config_patchlifetime": "Трайност на петното (с)",
+ "mod_epicloot_config_patchtickinterval": "Интервал на петното (с)",
+ "mod_epicloot_config_speedfloor": "Долна граница на скоростта",
+ "mod_epicloot_config_batlifetime": "Трайност на прилепа (с)",
+ "mod_epicloot_config_summoncountoffset": "Отместване на броя призовани",
+ "mod_epicloot_config_concurrentpersummon": "Макс. едновременни на призоваване",
+ "mod_epicloot_config_maxreduction": "Макс. намаляване",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% от макс. здраве за стойност",
+ "mod_epicloot_config_minhealthreduction": "Мин. намаляване на здравето",
+ "mod_epicloot_config_carryweightpervalue": "Товароносимост за стойност",
+ "mod_epicloot_config_minresultingcarryweight": "Мин. товароносимост",
+ "mod_epicloot_config_weightthreshold": "Праг на теглото",
+ "mod_epicloot_config_weightperbonus": "Тегло за бонус",
+ "mod_epicloot_config_eitrcostperdamage": "Разход на ейтър за щета",
+ "mod_epicloot_config_minskilllevel": "Мин. ниво на умението",
+ "mod_epicloot_config_levelweightexponent": "Експонента на тежестта на нивото",
+ "mod_epicloot_config_maxlevelupsperproc": "Макс. повишения на ниво за задействане",
+ "mod_epicloot_config_maxmultiplier": "Макс. множител на плячката",
+ "mod_epicloot_config_maxunitsperentry": "Макс. бройки на запис",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Макс. добавени бройки за убийство",
+ "mod_epicloot_config_rollbase": "Основа на хвърлянето",
+ "mod_epicloot_config_rollpervalue": "Хвърляне за стойност",
+ "mod_epicloot_config_rollcurve": "Крива на хвърлянето",
+ "mod_epicloot_config_floorfraction": "Дял на долната граница",
+ "mod_epicloot_config_chitin": "Хитин",
+ "mod_epicloot_config_copperscrap": "Меден скрап",
+ "mod_epicloot_config_ironscrap": "Железен скрап",
+ "mod_epicloot_config_runestoneepic": "Епичен рунен камък",
+ "mod_epicloot_config_runestonelegendary": "Легендарен рунен камък",
"mod_epicloot_me_modifylowhealth_config_max": "Максимален праг на ниско здраве",
"mod_epicloot_me_riches_config_coins": "Стойност на монетите",
"mod_epicloot_me_riches_config_amber": "Стойност на кехлибара",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(завинаги)",
"mod_epicloot_legendary_brokkrgift": "Легендарен дар на Брок",
"mod_epicloot_mythic_brokkrgift": "Митичен дар на Брок",
+ "mod_epicloot_ancient_brokkrgift": "Древен дар на Брок",
"mod_epicloot_slotchisel_introduce": "Изключително мощен артефакт, за който се твърди, че вгражда гнездо за отломки във всеки магически предмет.",
"mod_epicloot_slotchisel_hint": "Плъзнете върху магически предмет, за да добавите гнездо за отломки",
"mod_epicloot_slotchisel_confirm_title": "Да се добави ли гнездо за отломки?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Всяко претъркулване при отклоняване премахва {0} секунди от ефектите ти на отрова и горене.",
"mod_epicloot_me_stormrider_display": "+{0} % скорост на движение при буря",
"mod_epicloot_me_stormrider_desc": "+{0} % скорост на движение, докато бушува буря.",
- "mod_epicloot_me_shockingcharge_display": "Поразяващ заряд",
- "mod_epicloot_me_shockingcharge_desc": "Ударите ти в бой натрупват заряди. При пълен заряд следващият удар отприщва ударна вълна от мълнии напред, която нанася част от щетите на предишните ти удари.",
- "mod_epicloot_me_forestsaid_display": "Помощта на гората",
- "mod_epicloot_me_forestsaid_desc": "Когато те ударят, изригват оплитащи корени и обездвижват враговете наблизо.",
- "mod_epicloot_me_corpserot_display": "Трупно гниене",
- "mod_epicloot_me_corpserot_desc": "Враговете, които убиваш, могат да се пръснат в облак от отрова, тровейки противниците наблизо.",
- "mod_epicloot_me_icyretribution_display": "Ледено възмездие",
- "mod_epicloot_me_icyretribution_desc": "Когато те ударят, около теб избухва ледена нова. Презареждането се мащабира с рядкостта.",
+ "mod_epicloot_me_shockingcharge_display": "Поразяващ заряд: на всеки {0:0.#} удара",
+ "mod_epicloot_me_shockingcharge_desc": "На всеки {0:0.#} удара в бой следващият удар отприщва ударна вълна от мълнии напред, която нанася {1:0.#}% от нанесените с тях щети. По-високата рядкост изисква по-малко удари.",
+ "mod_epicloot_me_forestsaid_display": "Помощта на гората: {0:0.#} м, {1:0.#} с презареждане",
+ "mod_epicloot_me_forestsaid_desc": "Когато те ударят, изригват оплитащи корени и обездвижват враговете в радиус от {0:0.#} метра. По-високата рядкост достига по-далеч и се презарежда за {1:0.#} секунди.",
+ "mod_epicloot_me_corpserot_display": "Трупно гниене: {0:0.#} отрова",
+ "mod_epicloot_me_corpserot_desc": "Враговете, които убиваш, могат да се пръснат в облак от отрова, нанасяйки {0:0.#} отровни щети на противниците наблизо.",
+ "mod_epicloot_me_icyretribution_display": "Ледено възмездие: {0:0.#} мраз",
+ "mod_epicloot_me_icyretribution_desc": "Когато те ударят, около теб избухва ледена нова, нанасяща {0:0.#} мразовити щети. По-високата рядкост удря по-силно и се презарежда по-бързо.",
"mod_epicloot_me_meteorsummoner_display": "Призовател на метеори: {0}× щетите от удара",
"mod_epicloot_me_meteorsummoner_desc": "На всеки {1} удара с оръжие следващият ти удар сваля огнен метеор върху целта, който нанася {0}× щетите на този удар като огън.",
- "mod_epicloot_me_everflow_display": "Вечният поток на Кралицата",
- "mod_epicloot_me_everflow_desc": "Убиването на създание дава натрупващ се ефект, който подсилва възстановяването ти на здраве, издръжливост и ейтър.",
- "mod_epicloot_me_necroticfire_display": "Некротичен огън",
- "mod_epicloot_me_necroticfire_desc": "Премахва всички физически щети от оръжието ти и добавя цялата тази стойност и като огнени, и като отровни щети.",
+ "mod_epicloot_me_everflow_display": "Вечният поток на Кралицата: +{0:0.#}% възстановяване на слой",
+ "mod_epicloot_me_everflow_desc": "Убиването на създание дава натрупващ се ефект, който подсилва възстановяването ти на здраве, издръжливост и ейтър с {0:0.#}% на слой, до {1:0.#} слоя.",
+ "mod_epicloot_me_necroticfire_display": "Некротичен огън: +{0:0.#}% огън и отрова",
+ "mod_epicloot_me_necroticfire_desc": "Премахва всички физически щети от оръжието ти и добавя {0:0.#}% от тях като огнени щети и същото количество като отровни.",
"mod_epicloot_circlet": "обръч",
"mod_epicloot_belt": "колан",
"mod_epicloot_remains": "остатъци",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Легендарен {0}",
"mod_epicloot_basicmythicnameformat": "Митичен {0}",
+ "mod_epicloot_basicancientnameformat": "Древен {0}",
"mod_epicloot_rare_prefix1": "Странен",
"mod_epicloot_rare_prefix2": "Рядък",
"mod_epicloot_rare_prefix3": "Криптичен",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Епичен",
"mod_epicloot_Legendary": "Легендарен",
"mod_epicloot_Mythic": "Митичен",
+ "mod_epicloot_Ancient": "Древен",
"mod_epicloot_search_placeholder": "Търсене...",
"mod_epicloot_no_active_adventures": "Няма активни приключения",
"mod_epicloot_set_bonuses": "Комплектни бонуси",
diff --git a/EpicLoot/localizations/Chinese.json b/EpicLoot/localizations/Chinese.json
index 8819dce30..9e71ddf98 100644
--- a/EpicLoot/localizations/Chinese.json
+++ b/EpicLoot/localizations/Chinese.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "燧石",
"mod_epicloot_config_serpentscale": "海蛇鳞片",
"mod_epicloot_config_triplechance": "三重渔获几率",
- "mod_epicloot_config_secondspercent": "每 1% 对应秒数",
+ "mod_epicloot_config_secondsperpercent": "每 1% 对应秒数",
"mod_epicloot_config_procchance": "触发几率",
"mod_epicloot_config_bonusrollsmin": "最少额外掉落掷数",
"mod_epicloot_config_bonusrollsmax": "最多额外掉落掷数",
+ "mod_epicloot_config_cooldown": "冷却(秒)",
+ "mod_epicloot_config_basecooldown": "基础冷却(秒)",
+ "mod_epicloot_config_cooldownperrarity": "每稀有度冷却(秒)",
+ "mod_epicloot_config_cooldownpervalue": "每点数值冷却(秒)",
+ "mod_epicloot_config_buffduration": "持续时间(秒)",
+ "mod_epicloot_config_tickinterval": "触发间隔(秒)",
+ "mod_epicloot_config_radius": "半径(米)",
+ "mod_epicloot_config_baseradius": "基础半径(米)",
+ "mod_epicloot_config_radiuspertier": "每点数值半径(米)",
+ "mod_epicloot_config_maxcharges": "充能所需命中",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "充能所需命中(基础)",
+ "mod_epicloot_config_chargespervalue": "每点数值减少命中",
+ "mod_epicloot_config_mincharges": "最少充能命中",
+ "mod_epicloot_config_mincooldown": "最短冷却(秒)",
+ "mod_epicloot_config_damagepertrigger": "每次触发伤害",
+ "mod_epicloot_config_eitrpertrigger": "每次触发 Eitr",
+ "mod_epicloot_config_damagemultiplier": "伤害系数",
+ "mod_epicloot_config_damagefraction": "伤害比例",
+ "mod_epicloot_config_damagepervalue": "每点数值伤害",
+ "mod_epicloot_config_frostpertier": "每点数值冰霜伤害",
+ "mod_epicloot_config_poisonpertier": "每点数值毒素伤害",
+ "mod_epicloot_config_spawnheight": "生成高度(米)",
+ "mod_epicloot_config_spawnradius": "生成半径(米)",
+ "mod_epicloot_config_spawninterval": "生成间隔(秒)",
+ "mod_epicloot_config_mindistance": "最短发射距离(米)",
+ "mod_epicloot_config_maxdistance": "最远发射距离(米)",
+ "mod_epicloot_config_projectilespeed": "弹体速度(米/秒)",
+ "mod_epicloot_config_explosionradius": "爆炸半径(米)",
+ "mod_epicloot_config_conelength": "锥形长度(米)",
+ "mod_epicloot_config_conemaxwidth": "锥形宽度(米)",
+ "mod_epicloot_config_dischargeignorewindow": "放电忽略窗口(秒)",
+ "mod_epicloot_config_minmovespeed": "最低移动速度",
+ "mod_epicloot_config_patchradius": "区域半径(米)",
+ "mod_epicloot_config_patchlifetime": "区域持续时间(秒)",
+ "mod_epicloot_config_patchtickinterval": "区域触发间隔(秒)",
+ "mod_epicloot_config_speedfloor": "速度下限",
+ "mod_epicloot_config_batlifetime": "蝙蝠存在时间(秒)",
+ "mod_epicloot_config_summoncountoffset": "召唤数量偏移",
+ "mod_epicloot_config_concurrentpersummon": "每次召唤最大同时存在数",
+ "mod_epicloot_config_maxreduction": "最大减免",
+ "mod_epicloot_config_maxhealthpercentpervalue": "每点数值最大生命百分比",
+ "mod_epicloot_config_minhealthreduction": "最小生命降低",
+ "mod_epicloot_config_carryweightpervalue": "每点数值携带重量",
+ "mod_epicloot_config_minresultingcarryweight": "最低携带重量",
+ "mod_epicloot_config_weightthreshold": "重量阈值",
+ "mod_epicloot_config_weightperbonus": "每份加成所需重量",
+ "mod_epicloot_config_eitrcostperdamage": "每点伤害的 Eitr 消耗",
+ "mod_epicloot_config_minskilllevel": "最低技能等级",
+ "mod_epicloot_config_levelweightexponent": "等级权重指数",
+ "mod_epicloot_config_maxlevelupsperproc": "每次触发最多升级次数",
+ "mod_epicloot_config_maxmultiplier": "最大掉落倍率",
+ "mod_epicloot_config_maxunitsperentry": "每条目最大数量",
+ "mod_epicloot_config_maxaddedunitsperdeath": "每次击杀最大追加数量",
+ "mod_epicloot_config_rollbase": "基础掷数",
+ "mod_epicloot_config_rollpervalue": "每点数值掷数",
+ "mod_epicloot_config_rollcurve": "掷数曲线",
+ "mod_epicloot_config_floorfraction": "下限比例",
+ "mod_epicloot_config_chitin": "甲壳素",
+ "mod_epicloot_config_copperscrap": "铜碎屑",
+ "mod_epicloot_config_ironscrap": "铁碎屑",
+ "mod_epicloot_config_runestoneepic": "史诗符文",
+ "mod_epicloot_config_runestonelegendary": "传奇符文",
"mod_epicloot_me_modifylowhealth_config_max": "低生命阈值上限",
"mod_epicloot_me_riches_config_coins": "金币价值",
"mod_epicloot_me_riches_config_amber": "琥珀价值",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(永久)",
"mod_epicloot_legendary_brokkrgift": "传奇布洛克的赠礼",
"mod_epicloot_mythic_brokkrgift": "神话布洛克的赠礼",
+ "mod_epicloot_ancient_brokkrgift": "远古布洛克的赠礼",
"mod_epicloot_slotchisel_introduce": "一件极为强大的神器,据说能为任意魔法物品嵌入一个碎片槽。",
"mod_epicloot_slotchisel_hint": "拖到魔法物品上以添加碎片槽",
"mod_epicloot_slotchisel_confirm_title": "添加碎片槽?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "每次翻滚闪避移除毒素与燃烧效果 {0} 秒的剩余时间。",
"mod_epicloot_me_stormrider_display": "风暴中移动速度 +{0}%",
"mod_epicloot_me_stormrider_desc": "风暴肆虐时移动速度 +{0}%。",
- "mod_epicloot_me_shockingcharge_display": "震击充能",
- "mod_epicloot_me_shockingcharge_desc": "战斗中每次命中都会积累充能。充满后,你的下一次打击会向前释放一道闪电冲击波,造成上一次命中伤害的一部分。",
- "mod_epicloot_me_forestsaid_display": "森林之助",
- "mod_epicloot_me_forestsaid_desc": "受到攻击时,缠绕的根须会破土而出,抓住附近的敌人。",
- "mod_epicloot_me_corpserot_display": "尸腐",
- "mod_epicloot_me_corpserot_desc": "被击杀的敌人可能炸开一团毒云,使附近的敌人中毒。",
- "mod_epicloot_me_icyretribution_display": "寒冰惩戒",
- "mod_epicloot_me_icyretribution_desc": "受到攻击时在你周围引发一场冰霜爆发。冷却时间随稀有度而变化。",
+ "mod_epicloot_me_shockingcharge_display": "震击充能:每 {0:0.#} 次命中",
+ "mod_epicloot_me_shockingcharge_desc": "战斗中每命中 {0:0.#} 次,下一次打击会向前释放一道闪电冲击波,造成这些命中所造成伤害的 {1:0.#}%。稀有度越高,所需命中次数越少。",
+ "mod_epicloot_me_forestsaid_display": "森林之助:{0:0.#} 米,冷却 {1:0.#} 秒",
+ "mod_epicloot_me_forestsaid_desc": "受到攻击时,缠绕的根须会破土而出,定身 {0:0.#} 米内的敌人。稀有度越高,范围越大,冷却时间为 {1:0.#} 秒。",
+ "mod_epicloot_me_corpserot_display": "尸腐:{0:0.#} 毒素",
+ "mod_epicloot_me_corpserot_desc": "被击杀的敌人可能炸开一团毒云,对附近的敌人造成 {0:0.#} 点毒素伤害。",
+ "mod_epicloot_me_icyretribution_display": "寒冰惩戒:{0:0.#} 冰霜",
+ "mod_epicloot_me_icyretribution_desc": "受到攻击时在你周围引发一场冰霜爆发,造成 {0:0.#} 点冰霜伤害。稀有度越高,伤害越高,冷却也越短。",
"mod_epicloot_me_meteorsummoner_display": "陨石召唤者:命中伤害的 {0} 倍",
"mod_epicloot_me_meteorsummoner_desc": "此武器每命中 {1} 次,下一次打击便会在目标身上召来一颗燃烧的陨石,造成该次打击 {0} 倍的火焰伤害。",
- "mod_epicloot_me_everflow_display": "女王的永流",
- "mod_epicloot_me_everflow_desc": "击杀生物可获得一层增益,提升生命、耐力与 Eitr 的回复。",
- "mod_epicloot_me_necroticfire_display": "腐死之火",
- "mod_epicloot_me_necroticfire_desc": "移除武器的全部物理伤害,并将其等量地同时追加为火焰伤害与毒素伤害。",
+ "mod_epicloot_me_everflow_display": "女王的永流:每层 +{0:0.#}% 回复",
+ "mod_epicloot_me_everflow_desc": "击杀生物可获得一层增益,每层提升生命、耐力与 Eitr 回复 {0:0.#}%,最多 {1:0.#} 层。",
+ "mod_epicloot_me_necroticfire_display": "腐死之火:+{0:0.#}% 火焰与毒素",
+ "mod_epicloot_me_necroticfire_desc": "移除武器的全部物理伤害,并将其中的 {0:0.#}% 追加为火焰伤害,同时以相同数值追加为毒素伤害。",
"mod_epicloot_circlet": "头环",
"mod_epicloot_belt": "腰带",
"mod_epicloot_remains": "遗物",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "传奇 {0}",
"mod_epicloot_basicmythicnameformat": "神话 {0}",
+ "mod_epicloot_basicancientnameformat": "远古 {0}",
"mod_epicloot_rare_prefix1": "奇怪",
"mod_epicloot_rare_prefix2": "稀有",
"mod_epicloot_rare_prefix3": "神秘",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "史诗级",
"mod_epicloot_Legendary": "传奇",
"mod_epicloot_Mythic": "神话般的",
+ "mod_epicloot_Ancient": "远古",
"mod_epicloot_search_placeholder": "搜索...",
"mod_epicloot_no_active_adventures": "没有正在进行的冒险",
"mod_epicloot_set_bonuses": "套装加成",
diff --git a/EpicLoot/localizations/Chinese_Trad.json b/EpicLoot/localizations/Chinese_Trad.json
index c5067dd55..d36c9e9f8 100644
--- a/EpicLoot/localizations/Chinese_Trad.json
+++ b/EpicLoot/localizations/Chinese_Trad.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "燧石",
"mod_epicloot_config_serpentscale": "海蛇鱗片",
"mod_epicloot_config_triplechance": "三重漁獲機率",
- "mod_epicloot_config_secondspercent": "每 1% 對應秒數",
+ "mod_epicloot_config_secondsperpercent": "每 1% 對應秒數",
"mod_epicloot_config_procchance": "觸發機率",
"mod_epicloot_config_bonusrollsmin": "最少額外掉落擲數",
"mod_epicloot_config_bonusrollsmax": "最多額外掉落擲數",
+ "mod_epicloot_config_cooldown": "冷卻(秒)",
+ "mod_epicloot_config_basecooldown": "基礎冷卻(秒)",
+ "mod_epicloot_config_cooldownperrarity": "每稀有度冷卻(秒)",
+ "mod_epicloot_config_cooldownpervalue": "每點數值冷卻(秒)",
+ "mod_epicloot_config_buffduration": "持續時間(秒)",
+ "mod_epicloot_config_tickinterval": "觸發間隔(秒)",
+ "mod_epicloot_config_radius": "半徑(公尺)",
+ "mod_epicloot_config_baseradius": "基礎半徑(公尺)",
+ "mod_epicloot_config_radiuspertier": "每點數值半徑(公尺)",
+ "mod_epicloot_config_maxcharges": "充能所需命中",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "充能所需命中(基礎)",
+ "mod_epicloot_config_chargespervalue": "每點數值減少命中",
+ "mod_epicloot_config_mincharges": "最少充能命中",
+ "mod_epicloot_config_mincooldown": "最短冷卻(秒)",
+ "mod_epicloot_config_damagepertrigger": "每次觸發傷害",
+ "mod_epicloot_config_eitrpertrigger": "每次觸發 Eitr",
+ "mod_epicloot_config_damagemultiplier": "傷害係數",
+ "mod_epicloot_config_damagefraction": "傷害比例",
+ "mod_epicloot_config_damagepervalue": "每點數值傷害",
+ "mod_epicloot_config_frostpertier": "每點數值冰霜傷害",
+ "mod_epicloot_config_poisonpertier": "每點數值毒素傷害",
+ "mod_epicloot_config_spawnheight": "生成高度(公尺)",
+ "mod_epicloot_config_spawnradius": "生成半徑(公尺)",
+ "mod_epicloot_config_spawninterval": "生成間隔(秒)",
+ "mod_epicloot_config_mindistance": "最短發射距離(公尺)",
+ "mod_epicloot_config_maxdistance": "最遠發射距離(公尺)",
+ "mod_epicloot_config_projectilespeed": "彈體速度(公尺/秒)",
+ "mod_epicloot_config_explosionradius": "爆炸半徑(公尺)",
+ "mod_epicloot_config_conelength": "錐形長度(公尺)",
+ "mod_epicloot_config_conemaxwidth": "錐形寬度(公尺)",
+ "mod_epicloot_config_dischargeignorewindow": "放電忽略窗口(秒)",
+ "mod_epicloot_config_minmovespeed": "最低移動速度",
+ "mod_epicloot_config_patchradius": "區域半徑(公尺)",
+ "mod_epicloot_config_patchlifetime": "區域持續時間(秒)",
+ "mod_epicloot_config_patchtickinterval": "區域觸發間隔(秒)",
+ "mod_epicloot_config_speedfloor": "速度下限",
+ "mod_epicloot_config_batlifetime": "蝙蝠存在時間(秒)",
+ "mod_epicloot_config_summoncountoffset": "召喚數量偏移",
+ "mod_epicloot_config_concurrentpersummon": "每次召喚最大同時存在數",
+ "mod_epicloot_config_maxreduction": "最大減免",
+ "mod_epicloot_config_maxhealthpercentpervalue": "每點數值最大生命百分比",
+ "mod_epicloot_config_minhealthreduction": "最小生命降低",
+ "mod_epicloot_config_carryweightpervalue": "每點數值負重量",
+ "mod_epicloot_config_minresultingcarryweight": "最低負重量",
+ "mod_epicloot_config_weightthreshold": "重量門檻",
+ "mod_epicloot_config_weightperbonus": "每份加成所需重量",
+ "mod_epicloot_config_eitrcostperdamage": "每點傷害的 Eitr 消耗",
+ "mod_epicloot_config_minskilllevel": "最低技能等級",
+ "mod_epicloot_config_levelweightexponent": "等級權重指數",
+ "mod_epicloot_config_maxlevelupsperproc": "每次觸發最多升級次數",
+ "mod_epicloot_config_maxmultiplier": "最大掉落倍率",
+ "mod_epicloot_config_maxunitsperentry": "每條目最大數量",
+ "mod_epicloot_config_maxaddedunitsperdeath": "每次擊殺最大追加數量",
+ "mod_epicloot_config_rollbase": "基礎擲數",
+ "mod_epicloot_config_rollpervalue": "每點數值擲數",
+ "mod_epicloot_config_rollcurve": "擲數曲線",
+ "mod_epicloot_config_floorfraction": "下限比例",
+ "mod_epicloot_config_chitin": "甲殼質",
+ "mod_epicloot_config_copperscrap": "廢銅",
+ "mod_epicloot_config_ironscrap": "廢鐵",
+ "mod_epicloot_config_runestoneepic": "史詩符石",
+ "mod_epicloot_config_runestonelegendary": "傳奇符石",
"mod_epicloot_me_modifylowhealth_config_max": "低生命門檻上限",
"mod_epicloot_me_riches_config_coins": "金幣價值",
"mod_epicloot_me_riches_config_amber": "琥珀價值",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(永久)",
"mod_epicloot_legendary_brokkrgift": "傳奇布洛克的贈禮",
"mod_epicloot_mythic_brokkrgift": "神話布洛克的贈禮",
+ "mod_epicloot_ancient_brokkrgift": "遠古布洛克的贈禮",
"mod_epicloot_slotchisel_introduce": "一件極為強大的神器,據說能為任意魔法物品嵌入一個碎片槽。",
"mod_epicloot_slotchisel_hint": "拖到魔法物品上以新增碎片槽",
"mod_epicloot_slotchisel_confirm_title": "新增碎片槽?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "每次翻滾閃避移除毒素與燃燒效果 {0} 秒的剩餘時間。",
"mod_epicloot_me_stormrider_display": "風暴中移動速度 +{0}%",
"mod_epicloot_me_stormrider_desc": "風暴肆虐時移動速度 +{0}%。",
- "mod_epicloot_me_shockingcharge_display": "震擊充能",
- "mod_epicloot_me_shockingcharge_desc": "戰鬥中每次命中都會累積充能。充滿後,你的下一次打擊會向前釋放一道閃電衝擊波,造成上一次命中傷害的一部分。",
- "mod_epicloot_me_forestsaid_display": "森林之助",
- "mod_epicloot_me_forestsaid_desc": "受到攻擊時,纏繞的根鬚會破土而出,抓住附近的敵人。",
- "mod_epicloot_me_corpserot_display": "屍腐",
- "mod_epicloot_me_corpserot_desc": "被擊殺的敵人可能炸開一團毒雲,使附近的敵人中毒。",
- "mod_epicloot_me_icyretribution_display": "寒冰懲戒",
- "mod_epicloot_me_icyretribution_desc": "受到攻擊時在你周圍引發一場冰霜爆發。冷卻時間隨稀有度而變化。",
+ "mod_epicloot_me_shockingcharge_display": "震擊充能:每 {0:0.#} 次命中",
+ "mod_epicloot_me_shockingcharge_desc": "戰鬥中每命中 {0:0.#} 次,下一次打擊會向前釋放一道閃電衝擊波,造成這些命中所造成傷害的 {1:0.#}%。稀有度越高,所需命中次數越少。",
+ "mod_epicloot_me_forestsaid_display": "森林之助:{0:0.#} 公尺,冷卻 {1:0.#} 秒",
+ "mod_epicloot_me_forestsaid_desc": "受到攻擊時,纏繞的根鬚會破土而出,定身 {0:0.#} 公尺內的敵人。稀有度越高,範圍越大,冷卻時間為 {1:0.#} 秒。",
+ "mod_epicloot_me_corpserot_display": "屍腐:{0:0.#} 毒素",
+ "mod_epicloot_me_corpserot_desc": "被擊殺的敵人可能炸開一團毒雲,對附近的敵人造成 {0:0.#} 點毒素傷害。",
+ "mod_epicloot_me_icyretribution_display": "寒冰懲戒:{0:0.#} 冰霜",
+ "mod_epicloot_me_icyretribution_desc": "受到攻擊時在你周圍引發一場冰霜爆發,造成 {0:0.#} 點冰霜傷害。稀有度越高,傷害越高,冷卻也越短。",
"mod_epicloot_me_meteorsummoner_display": "隕石召喚者:命中傷害的 {0} 倍",
"mod_epicloot_me_meteorsummoner_desc": "此武器每命中 {1} 次,下一次打擊便會在目標身上召來一顆燃燒的隕石,造成該次打擊 {0} 倍的火焰傷害。",
- "mod_epicloot_me_everflow_display": "女王的永流",
- "mod_epicloot_me_everflow_desc": "擊殺生物可獲得一層增益,提升生命、耐力與 Eitr 的回復。",
- "mod_epicloot_me_necroticfire_display": "腐死之火",
- "mod_epicloot_me_necroticfire_desc": "移除武器的全部物理傷害,並將其等量地同時追加為火焰傷害與毒素傷害。",
+ "mod_epicloot_me_everflow_display": "女王的永流:每層 +{0:0.#}% 回復",
+ "mod_epicloot_me_everflow_desc": "擊殺生物可獲得一層增益,每層提升生命、耐力與 Eitr 回復 {0:0.#}%,最多 {1:0.#} 層。",
+ "mod_epicloot_me_necroticfire_display": "腐死之火:+{0:0.#}% 火焰與毒素",
+ "mod_epicloot_me_necroticfire_desc": "移除武器的全部物理傷害,並將其中的 {0:0.#}% 追加為火焰傷害,同時以相同數值追加為毒素傷害。",
"mod_epicloot_circlet": "頭飾",
"mod_epicloot_belt": "腰帶",
"mod_epicloot_remains": "殘骸",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": " {0} {1}",
"mod_epicloot_basiclegendarynameformat": "傳奇 {0}",
"mod_epicloot_basicmythicnameformat": "神話 {0}",
+ "mod_epicloot_basicancientnameformat": "遠古 {0}",
"mod_epicloot_rare_prefix1": "奇異的",
"mod_epicloot_rare_prefix2": "罕見的",
"mod_epicloot_rare_prefix3": "神秘的",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "史诗般的",
"mod_epicloot_Legendary": "傳奇",
"mod_epicloot_Mythic": "神話般的",
+ "mod_epicloot_Ancient": "遠古",
"mod_epicloot_search_placeholder": "搜尋...",
"mod_epicloot_no_active_adventures": "沒有進行中的冒險",
"mod_epicloot_set_bonuses": "套裝加成",
diff --git a/EpicLoot/localizations/Croatian.json b/EpicLoot/localizations/Croatian.json
index cc99cd75f..58623904b 100644
--- a/EpicLoot/localizations/Croatian.json
+++ b/EpicLoot/localizations/Croatian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Kremen",
"mod_epicloot_config_serpentscale": "Ljuska morske zmije",
"mod_epicloot_config_triplechance": "Šansa za trostruki ulov",
- "mod_epicloot_config_secondspercent": "Sekundi po 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekundi po 1 %",
"mod_epicloot_config_procchance": "Šansa aktivacije",
"mod_epicloot_config_bonusrollsmin": "Min. bonus bacanja za plijen",
"mod_epicloot_config_bonusrollsmax": "Maks. bonus bacanja za plijen",
+ "mod_epicloot_config_cooldown": "Hlađenje (s)",
+ "mod_epicloot_config_basecooldown": "Osnovno hlađenje (s)",
+ "mod_epicloot_config_cooldownperrarity": "Hlađenje po rijetkosti (s)",
+ "mod_epicloot_config_cooldownpervalue": "Hlađenje po vrijednosti (s)",
+ "mod_epicloot_config_buffduration": "Trajanje (s)",
+ "mod_epicloot_config_tickinterval": "Interval otkucaja (s)",
+ "mod_epicloot_config_radius": "Polumjer (m)",
+ "mod_epicloot_config_baseradius": "Osnovni polumjer (m)",
+ "mod_epicloot_config_radiuspertier": "Polumjer po vrijednosti (m)",
+ "mod_epicloot_config_maxcharges": "Udaraca do punjenja",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Udaraca do punjenja (osnovno)",
+ "mod_epicloot_config_chargespervalue": "Ušteda udaraca po vrijednosti",
+ "mod_epicloot_config_mincharges": "Min. udaraca do punjenja",
+ "mod_epicloot_config_mincooldown": "Min. hlađenje (s)",
+ "mod_epicloot_config_damagepertrigger": "Šteta po okidanju",
+ "mod_epicloot_config_eitrpertrigger": "Eitr po okidanju",
+ "mod_epicloot_config_damagemultiplier": "Množitelj štete",
+ "mod_epicloot_config_damagefraction": "Udio štete",
+ "mod_epicloot_config_damagepervalue": "Šteta po vrijednosti",
+ "mod_epicloot_config_frostpertier": "Ledena šteta po vrijednosti",
+ "mod_epicloot_config_poisonpertier": "Otrovna šteta po vrijednosti",
+ "mod_epicloot_config_spawnheight": "Visina stvaranja (m)",
+ "mod_epicloot_config_spawnradius": "Polumjer stvaranja (m)",
+ "mod_epicloot_config_spawninterval": "Interval stvaranja (s)",
+ "mod_epicloot_config_mindistance": "Min. udaljenost izbacivanja (m)",
+ "mod_epicloot_config_maxdistance": "Maks. udaljenost izbacivanja (m)",
+ "mod_epicloot_config_projectilespeed": "Brzina projektila (m/s)",
+ "mod_epicloot_config_explosionradius": "Polumjer eksplozije (m)",
+ "mod_epicloot_config_conelength": "Duljina stošca (m)",
+ "mod_epicloot_config_conemaxwidth": "Širina stošca (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Prozor zanemarivanja pražnjenja (s)",
+ "mod_epicloot_config_minmovespeed": "Min. brzina kretanja",
+ "mod_epicloot_config_patchradius": "Polumjer mrlje (m)",
+ "mod_epicloot_config_patchlifetime": "Trajanje mrlje (s)",
+ "mod_epicloot_config_patchtickinterval": "Interval otkucaja mrlje (s)",
+ "mod_epicloot_config_speedfloor": "Donja granica brzine",
+ "mod_epicloot_config_batlifetime": "Trajanje šišmiša (s)",
+ "mod_epicloot_config_summoncountoffset": "Pomak broja prizvanih",
+ "mod_epicloot_config_concurrentpersummon": "Maks. istodobnih po prizivanju",
+ "mod_epicloot_config_maxreduction": "Maks. smanjenje",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% maks. zdravlja po vrijednosti",
+ "mod_epicloot_config_minhealthreduction": "Min. smanjenje zdravlja",
+ "mod_epicloot_config_carryweightpervalue": "Nosivost po vrijednosti",
+ "mod_epicloot_config_minresultingcarryweight": "Min. nosivost",
+ "mod_epicloot_config_weightthreshold": "Prag težine",
+ "mod_epicloot_config_weightperbonus": "Težina po bonusu",
+ "mod_epicloot_config_eitrcostperdamage": "Trošak eitra po šteti",
+ "mod_epicloot_config_minskilllevel": "Min. razina vještine",
+ "mod_epicloot_config_levelweightexponent": "Eksponent težine razine",
+ "mod_epicloot_config_maxlevelupsperproc": "Maks. povećanja razine po okidanju",
+ "mod_epicloot_config_maxmultiplier": "Maks. množitelj plijena",
+ "mod_epicloot_config_maxunitsperentry": "Maks. jedinica po unosu",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Maks. dodanih jedinica po ubojstvu",
+ "mod_epicloot_config_rollbase": "Osnova bacanja",
+ "mod_epicloot_config_rollpervalue": "Bacanje po vrijednosti",
+ "mod_epicloot_config_rollcurve": "Krivulja bacanja",
+ "mod_epicloot_config_floorfraction": "Udio donje granice",
+ "mod_epicloot_config_chitin": "Hitin",
+ "mod_epicloot_config_copperscrap": "Bakreni otpad",
+ "mod_epicloot_config_ironscrap": "Željezni otpad",
+ "mod_epicloot_config_runestoneepic": "Epski runski kamen",
+ "mod_epicloot_config_runestonelegendary": "Legendarni runski kamen",
"mod_epicloot_me_modifylowhealth_config_max": "Najveći prag niskog zdravlja",
"mod_epicloot_me_riches_config_coins": "Vrijednost novčića",
"mod_epicloot_me_riches_config_amber": "Vrijednost jantara",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(trajno)",
"mod_epicloot_legendary_brokkrgift": "Legendaran Brokkrov dar",
"mod_epicloot_mythic_brokkrgift": "Mitski Brokkrov dar",
+ "mod_epicloot_ancient_brokkrgift": "Drevni Brokkrov dar",
"mod_epicloot_slotchisel_introduce": "Iznimno moćan artefakt za koji se govori da u bilo koji čarobni predmet ugrađuje utor za krhotine.",
"mod_epicloot_slotchisel_hint": "Povuci na čarobni predmet da dodaš utor za krhotine",
"mod_epicloot_slotchisel_confirm_title": "Dodati utor za krhotine?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Svako kotrljanje za izmicanje uklanja {0} sekundi s tvojih otrovnih i gorućih učinaka.",
"mod_epicloot_me_stormrider_display": "+{0} % brzine kretanja u olujama",
"mod_epicloot_me_stormrider_desc": "+{0} % brzine kretanja dok bjesni oluja.",
- "mod_epicloot_me_shockingcharge_display": "Šokantni naboj",
- "mod_epicloot_me_shockingcharge_desc": "Tvoji borbeni pogoci skupljaju naboje. Pri punom naboju sljedeći pogodak oslobađa munjeviti udarni val prema naprijed koji nanosi dio štete tvojih prethodnih pogodaka.",
- "mod_epicloot_me_forestsaid_display": "Pomoć šume",
- "mod_epicloot_me_forestsaid_desc": "Kada te pogode, izbijaju sputavajuće korijenje i imobiliziraju obližnje neprijatelje.",
- "mod_epicloot_me_corpserot_display": "Trupla trulež",
- "mod_epicloot_me_corpserot_desc": "Neprijatelji koje ubiješ mogu prsnuti u oblak otrova, trujući obližnje protivnike.",
- "mod_epicloot_me_icyretribution_display": "Ledena odmazda",
- "mod_epicloot_me_icyretribution_desc": "Kada te pogode, detoniraš ledenu novu oko sebe. Vrijeme oporavka skalira se s rijetkošću.",
+ "mod_epicloot_me_shockingcharge_display": "Šokantni naboj: svakih {0:0.#} pogodaka",
+ "mod_epicloot_me_shockingcharge_desc": "Svakih {0:0.#} borbenih pogodaka sljedeći pogodak oslobađa munjeviti udarni val prema naprijed koji nanosi {1:0.#}% štete tih pogodaka. Viša rijetkost traži manje pogodaka.",
+ "mod_epicloot_me_forestsaid_display": "Pomoć šume: {0:0.#} m, {1:0.#} s hlađenja",
+ "mod_epicloot_me_forestsaid_desc": "Kada te pogode, izbija sputavajuće korijenje i imobilizira neprijatelje u krugu od {0:0.#} metara. Viša rijetkost doseže dalje i puni se za {1:0.#} sekundi.",
+ "mod_epicloot_me_corpserot_display": "Trupla trulež: {0:0.#} otrova",
+ "mod_epicloot_me_corpserot_desc": "Neprijatelji koje ubiješ mogu prsnuti u oblak otrova, nanoseći {0:0.#} štete otrovom obližnjim protivnicima.",
+ "mod_epicloot_me_icyretribution_display": "Ledena odmazda: {0:0.#} mraza",
+ "mod_epicloot_me_icyretribution_desc": "Kada te pogode, detoniraš ledenu novu oko sebe koja nanosi {0:0.#} štete mrazom. Viša rijetkost udara jače i brže se oporavlja.",
"mod_epicloot_me_meteorsummoner_display": "Prizivač meteora: {0}× šteta pogotka",
"mod_epicloot_me_meteorsummoner_desc": "Svakih {1} pogodaka oružjem tvoj sljedeći pogodak priziva vatreni meteor na metu koji nanosi {0}× štetu tog pogotka kao vatru.",
- "mod_epicloot_me_everflow_display": "Vječni tok Kraljice",
- "mod_epicloot_me_everflow_desc": "Ubijanje stvorenja daje složivi bonus koji pojačava regeneraciju zdravlja, izdržljivosti i eitra.",
- "mod_epicloot_me_necroticfire_display": "Nekrotična vatra",
- "mod_epicloot_me_necroticfire_desc": "Uklanja svu fizičku štetu s tvog oružja i dodaje taj cijeli iznos i kao vatrenu i kao otrovnu štetu.",
+ "mod_epicloot_me_everflow_display": "Vječni tok Kraljice: +{0:0.#}% regeneracije po sloju",
+ "mod_epicloot_me_everflow_desc": "Ubijanje stvorenja daje složivi bonus koji pojačava regeneraciju zdravlja, izdržljivosti i eitra za {0:0.#}% po sloju, do {1:0.#} slojeva.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotična vatra: +{0:0.#}% vatre i otrova",
+ "mod_epicloot_me_necroticfire_desc": "Uklanja svu fizičku štetu s tvog oružja i dodaje {0:0.#}% toga kao vatrenu štetu te isto toliko kao otrovnu.",
"mod_epicloot_circlet": "čelenka",
"mod_epicloot_belt": "pojas",
"mod_epicloot_remains": "ostaci",
@@ -1021,7 +1084,7 @@
"mod_epicloot_bounties_suffix_svalroot": "Svalroot",
"mod_epicloot_bounties_suffix_swiftspite": "Swiftspite",
"mod_epicloot_bounties_suffix_the_abandoned": "the Abandoned",
- "mod_epicloot_bounties_suffix_the_ancient": "the Ancient",
+ "mod_epicloot_bounties_suffix_the_ancient": "Drevni",
"mod_epicloot_bounties_suffix_the_betrayer": "the Betrayer",
"mod_epicloot_bounties_suffix_the_brutal": "the Brutal",
"mod_epicloot_bounties_suffix_the_brute": "the Brute",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendarni {0}",
"mod_epicloot_basicmythicnameformat": "Mitološki {0}",
+ "mod_epicloot_basicancientnameformat": "Drevni {0}",
"mod_epicloot_rare_prefix1": "Čudan",
"mod_epicloot_rare_prefix2": "Redak",
"mod_epicloot_rare_prefix3": "Tajanstven",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epohalno",
"mod_epicloot_Legendary": "Legendaran",
"mod_epicloot_Mythic": "Mitski",
+ "mod_epicloot_Ancient": "Drevni",
"mod_epicloot_search_placeholder": "Pretraži...",
"mod_epicloot_no_active_adventures": "Nema aktivnih avantura",
"mod_epicloot_set_bonuses": "Set bonusi",
diff --git a/EpicLoot/localizations/Czech.json b/EpicLoot/localizations/Czech.json
index ba06e2da5..b739161d4 100644
--- a/EpicLoot/localizations/Czech.json
+++ b/EpicLoot/localizations/Czech.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Pazourek",
"mod_epicloot_config_serpentscale": "Šupina mořského hada",
"mod_epicloot_config_triplechance": "Šance na trojitý úlovek",
- "mod_epicloot_config_secondspercent": "Sekund na 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekund na 1 %",
"mod_epicloot_config_procchance": "Šance na spuštění",
"mod_epicloot_config_bonusrollsmin": "Min. bonusových hodů na kořist",
"mod_epicloot_config_bonusrollsmax": "Max. bonusových hodů na kořist",
+ "mod_epicloot_config_cooldown": "Nabíjení (s)",
+ "mod_epicloot_config_basecooldown": "Základní nabíjení (s)",
+ "mod_epicloot_config_cooldownperrarity": "Nabíjení podle vzácnosti (s)",
+ "mod_epicloot_config_cooldownpervalue": "Nabíjení podle hodnoty (s)",
+ "mod_epicloot_config_buffduration": "Trvání (s)",
+ "mod_epicloot_config_tickinterval": "Interval tiku (s)",
+ "mod_epicloot_config_radius": "Poloměr (m)",
+ "mod_epicloot_config_baseradius": "Základní poloměr (m)",
+ "mod_epicloot_config_radiuspertier": "Poloměr podle hodnoty (m)",
+ "mod_epicloot_config_maxcharges": "Zásahů k nabití",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Zásahů k nabití (základ)",
+ "mod_epicloot_config_chargespervalue": "Ušetřených zásahů podle hodnoty",
+ "mod_epicloot_config_mincharges": "Min. zásahů k nabití",
+ "mod_epicloot_config_mincooldown": "Min. nabíjení (s)",
+ "mod_epicloot_config_damagepertrigger": "Poškození na spuštění",
+ "mod_epicloot_config_eitrpertrigger": "Eitr na spuštění",
+ "mod_epicloot_config_damagemultiplier": "Násobitel poškození",
+ "mod_epicloot_config_damagefraction": "Podíl poškození",
+ "mod_epicloot_config_damagepervalue": "Poškození podle hodnoty",
+ "mod_epicloot_config_frostpertier": "Mrazivé poškození podle hodnoty",
+ "mod_epicloot_config_poisonpertier": "Jedovaté poškození podle hodnoty",
+ "mod_epicloot_config_spawnheight": "Výška zrodu (m)",
+ "mod_epicloot_config_spawnradius": "Poloměr zrodu (m)",
+ "mod_epicloot_config_spawninterval": "Interval zrodu (s)",
+ "mod_epicloot_config_mindistance": "Min. vzdálenost vypuštění (m)",
+ "mod_epicloot_config_maxdistance": "Max. vzdálenost vypuštění (m)",
+ "mod_epicloot_config_projectilespeed": "Rychlost projektilu (m/s)",
+ "mod_epicloot_config_explosionradius": "Poloměr výbuchu (m)",
+ "mod_epicloot_config_conelength": "Délka kužele (m)",
+ "mod_epicloot_config_conemaxwidth": "Šířka kužele (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Okno ignorování výboje (s)",
+ "mod_epicloot_config_minmovespeed": "Min. rychlost pohybu",
+ "mod_epicloot_config_patchradius": "Poloměr skvrny (m)",
+ "mod_epicloot_config_patchlifetime": "Trvání skvrny (s)",
+ "mod_epicloot_config_patchtickinterval": "Interval tiku skvrny (s)",
+ "mod_epicloot_config_speedfloor": "Dolní mez rychlosti",
+ "mod_epicloot_config_batlifetime": "Trvání netopýra (s)",
+ "mod_epicloot_config_summoncountoffset": "Posun počtu přivolaných",
+ "mod_epicloot_config_concurrentpersummon": "Max. současně na přivolání",
+ "mod_epicloot_config_maxreduction": "Max. snížení",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% max. zdraví podle hodnoty",
+ "mod_epicloot_config_minhealthreduction": "Min. snížení zdraví",
+ "mod_epicloot_config_carryweightpervalue": "Nosnost podle hodnoty",
+ "mod_epicloot_config_minresultingcarryweight": "Min. nosnost",
+ "mod_epicloot_config_weightthreshold": "Práh hmotnosti",
+ "mod_epicloot_config_weightperbonus": "Hmotnost na bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Náklady eitru na poškození",
+ "mod_epicloot_config_minskilllevel": "Min. úroveň dovednosti",
+ "mod_epicloot_config_levelweightexponent": "Exponent váhy úrovně",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. zvýšení úrovně na spuštění",
+ "mod_epicloot_config_maxmultiplier": "Max. násobitel kořisti",
+ "mod_epicloot_config_maxunitsperentry": "Max. jednotek na položku",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. přidaných jednotek na zabití",
+ "mod_epicloot_config_rollbase": "Základ hodu",
+ "mod_epicloot_config_rollpervalue": "Hod podle hodnoty",
+ "mod_epicloot_config_rollcurve": "Křivka hodu",
+ "mod_epicloot_config_floorfraction": "Podíl dolní meze",
+ "mod_epicloot_config_chitin": "Chitin",
+ "mod_epicloot_config_copperscrap": "Měděný šrot",
+ "mod_epicloot_config_ironscrap": "Železný šrot",
+ "mod_epicloot_config_runestoneepic": "Epický runový kámen",
+ "mod_epicloot_config_runestonelegendary": "Legendární runový kámen",
"mod_epicloot_me_modifylowhealth_config_max": "Maximální práh nízkého zdraví",
"mod_epicloot_me_riches_config_coins": "Hodnota mincí",
"mod_epicloot_me_riches_config_amber": "Hodnota jantaru",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(trvalé)",
"mod_epicloot_legendary_brokkrgift": "Legendární Brokkrův dar",
"mod_epicloot_mythic_brokkrgift": "Mystický Brokkrův dar",
+ "mod_epicloot_ancient_brokkrgift": "Starověký Brokkrův dar",
"mod_epicloot_slotchisel_introduce": "Nesmírně mocný artefakt, o němž se říká, že do libovolného magického předmětu vsadí slot na střepy.",
"mod_epicloot_slotchisel_hint": "Přetáhni na magický předmět a přidej slot na střepy",
"mod_epicloot_slotchisel_confirm_title": "Přidat slot na střepy?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Každý úhybový kotoul odebere {0} sekund z tvých jedových a hořících efektů.",
"mod_epicloot_me_stormrider_display": "+{0} % rychlosti pohybu v bouřích",
"mod_epicloot_me_stormrider_desc": "+{0} % rychlosti pohybu, dokud zuří bouře.",
- "mod_epicloot_me_shockingcharge_display": "Šokující náboj",
- "mod_epicloot_me_shockingcharge_desc": "Tvé zásahy v boji hromadí náboje. Při plném náboji další zásah vypustí dopřednou bleskovou rázovou vlnu, která způsobí část poškození tvých předchozích zásahů.",
- "mod_epicloot_me_forestsaid_display": "Pomoc lesa",
- "mod_epicloot_me_forestsaid_desc": "Když jsi zasažen, vyrazí spoutávající kořeny a znehybní nepřátele poblíž.",
- "mod_epicloot_me_corpserot_display": "Mrtvolný rozklad",
- "mod_epicloot_me_corpserot_desc": "Nepřátelé, které zabiješ, mohou puknout v oblak jedu a otrávit protivníky poblíž.",
- "mod_epicloot_me_icyretribution_display": "Ledová odplata",
- "mod_epicloot_me_icyretribution_desc": "Když jsi zasažen, odpálíš kolem sebe mrazivou novu. Doba obnovy se škáluje se vzácností.",
+ "mod_epicloot_me_shockingcharge_display": "Šokující náboj: každých {0:0.#} zásahů",
+ "mod_epicloot_me_shockingcharge_desc": "Každých {0:0.#} zásahů v boji vypustí další zásah dopřednou bleskovou rázovou vlnu, která způsobí {1:0.#} % poškození těchto zásahů. Vyšší vzácnost vyžaduje méně zásahů.",
+ "mod_epicloot_me_forestsaid_display": "Pomoc lesa: {0:0.#} m, {1:0.#} s nabíjení",
+ "mod_epicloot_me_forestsaid_desc": "Když jsi zasažen, vyrazí spoutávající kořeny a znehybní nepřátele do {0:0.#} metrů. Vyšší vzácnost dosáhne dál a nabije se za {1:0.#} sekund.",
+ "mod_epicloot_me_corpserot_display": "Mrtvolný rozklad: {0:0.#} jedu",
+ "mod_epicloot_me_corpserot_desc": "Nepřátelé, které zabiješ, mohou puknout v oblak jedu a způsobit protivníkům poblíž {0:0.#} poškození jedem.",
+ "mod_epicloot_me_icyretribution_display": "Ledová odplata: {0:0.#} mrazu",
+ "mod_epicloot_me_icyretribution_desc": "Když jsi zasažen, odpálíš kolem sebe mrazivou novu, která způsobí {0:0.#} poškození mrazem. Vyšší vzácnost udeří silněji a obnoví se dřív.",
"mod_epicloot_me_meteorsummoner_display": "Přivolávač meteorů: {0}× poškození zásahu",
"mod_epicloot_me_meteorsummoner_desc": "Každých {1} zásahů zbraní svolá tvůj další zásah na cíl ohnivý meteor, který způsobí {0}× poškození toho zásahu jako oheň.",
- "mod_epicloot_me_everflow_display": "Věčný tok Královny",
- "mod_epicloot_me_everflow_desc": "Zabití tvora udělí vrstvící se posílení, které zvyšuje regeneraci zdraví, výdrže a eitru.",
- "mod_epicloot_me_necroticfire_display": "Nekrotický oheň",
- "mod_epicloot_me_necroticfire_desc": "Odebere tvé zbrani veškeré fyzické poškození a přidá celou tuto hodnotu jako ohnivé i jedovaté poškození.",
+ "mod_epicloot_me_everflow_display": "Věčný tok Královny: +{0:0.#} % regenerace za vrstvu",
+ "mod_epicloot_me_everflow_desc": "Zabití tvora udělí vrstvící se posílení, které zvyšuje regeneraci zdraví, výdrže a eitru o {0:0.#} % za vrstvu, až do {1:0.#} vrstev.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotický oheň: +{0:0.#} % ohně a jedu",
+ "mod_epicloot_me_necroticfire_desc": "Odebere tvé zbrani veškeré fyzické poškození a přidá {0:0.#} % z něj jako ohnivé poškození a stejně tolik jako jedovaté.",
"mod_epicloot_circlet": "přilba",
"mod_epicloot_belt": "pás",
"mod_epicloot_remains": "zbytky",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendární {0}",
"mod_epicloot_basicmythicnameformat": "Mytický {0}",
+ "mod_epicloot_basicancientnameformat": "Starověký {0}",
"mod_epicloot_rare_prefix1": "Podivný",
"mod_epicloot_rare_prefix2": "Vzácný",
"mod_epicloot_rare_prefix3": "Tajemný",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "ký",
"mod_epicloot_Legendary": "Legendární",
"mod_epicloot_Mythic": "Mystický",
+ "mod_epicloot_Ancient": "Starověký",
"mod_epicloot_search_placeholder": "Hledat...",
"mod_epicloot_no_active_adventures": "Žádná aktivní dobrodružství",
"mod_epicloot_set_bonuses": "Bonusy za set",
diff --git a/EpicLoot/localizations/Danish.json b/EpicLoot/localizations/Danish.json
index cc18a449f..6012083dc 100644
--- a/EpicLoot/localizations/Danish.json
+++ b/EpicLoot/localizations/Danish.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Flint",
"mod_epicloot_config_serpentscale": "Søslangeskæl",
"mod_epicloot_config_triplechance": "Chance for tredobbelt fangst",
- "mod_epicloot_config_secondspercent": "Sekunder pr. 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekunder pr. 1 %",
"mod_epicloot_config_procchance": "Udløsningschance",
"mod_epicloot_config_bonusrollsmin": "Min. bonusslag for bytte",
"mod_epicloot_config_bonusrollsmax": "Maks. bonusslag for bytte",
+ "mod_epicloot_config_cooldown": "Nedkøling (s)",
+ "mod_epicloot_config_basecooldown": "Basisnedkøling (s)",
+ "mod_epicloot_config_cooldownperrarity": "Nedkøling pr. sjældenhed (s)",
+ "mod_epicloot_config_cooldownpervalue": "Nedkøling pr. værdi (s)",
+ "mod_epicloot_config_buffduration": "Varighed (s)",
+ "mod_epicloot_config_tickinterval": "Tikinterval (s)",
+ "mod_epicloot_config_radius": "Radius (m)",
+ "mod_epicloot_config_baseradius": "Basisradius (m)",
+ "mod_epicloot_config_radiuspertier": "Radius pr. værdi (m)",
+ "mod_epicloot_config_maxcharges": "Slag til opladning",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Slag til opladning (basis)",
+ "mod_epicloot_config_chargespervalue": "Sparede slag pr. værdi",
+ "mod_epicloot_config_mincharges": "Min. slag til opladning",
+ "mod_epicloot_config_mincooldown": "Min. nedkøling (s)",
+ "mod_epicloot_config_damagepertrigger": "Skade pr. udløsning",
+ "mod_epicloot_config_eitrpertrigger": "Eitr pr. udløsning",
+ "mod_epicloot_config_damagemultiplier": "Skademultiplikator",
+ "mod_epicloot_config_damagefraction": "Skadeandel",
+ "mod_epicloot_config_damagepervalue": "Skade pr. værdi",
+ "mod_epicloot_config_frostpertier": "Frostskade pr. værdi",
+ "mod_epicloot_config_poisonpertier": "Giftskade pr. værdi",
+ "mod_epicloot_config_spawnheight": "Spawnhøjde (m)",
+ "mod_epicloot_config_spawnradius": "Spawnradius (m)",
+ "mod_epicloot_config_spawninterval": "Spawninterval (s)",
+ "mod_epicloot_config_mindistance": "Min. udskydningsafstand (m)",
+ "mod_epicloot_config_maxdistance": "Maks. udskydningsafstand (m)",
+ "mod_epicloot_config_projectilespeed": "Projektilfart (m/s)",
+ "mod_epicloot_config_explosionradius": "Eksplosionsradius (m)",
+ "mod_epicloot_config_conelength": "Keglelængde (m)",
+ "mod_epicloot_config_conemaxwidth": "Keglebredde (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Ignoreringsvindue for udladning (s)",
+ "mod_epicloot_config_minmovespeed": "Min. bevægelsesfart",
+ "mod_epicloot_config_patchradius": "Feltradius (m)",
+ "mod_epicloot_config_patchlifetime": "Feltlevetid (s)",
+ "mod_epicloot_config_patchtickinterval": "Felttikinterval (s)",
+ "mod_epicloot_config_speedfloor": "Fartgulv",
+ "mod_epicloot_config_batlifetime": "Flagermusens levetid (s)",
+ "mod_epicloot_config_summoncountoffset": "Forskydning af tilkaldt antal",
+ "mod_epicloot_config_concurrentpersummon": "Maks. samtidige pr. tilkaldelse",
+ "mod_epicloot_config_maxreduction": "Maks. reduktion",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% af maks. helbred pr. værdi",
+ "mod_epicloot_config_minhealthreduction": "Min. helbredsreduktion",
+ "mod_epicloot_config_carryweightpervalue": "Bærevægt pr. værdi",
+ "mod_epicloot_config_minresultingcarryweight": "Min. bærevægt",
+ "mod_epicloot_config_weightthreshold": "Vægttærskel",
+ "mod_epicloot_config_weightperbonus": "Vægt pr. bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-omkostning pr. skade",
+ "mod_epicloot_config_minskilllevel": "Min. færdighedsniveau",
+ "mod_epicloot_config_levelweightexponent": "Eksponent for niveauvægt",
+ "mod_epicloot_config_maxlevelupsperproc": "Maks. niveaustigninger pr. udløsning",
+ "mod_epicloot_config_maxmultiplier": "Maks. byttemultiplikator",
+ "mod_epicloot_config_maxunitsperentry": "Maks. enheder pr. post",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Maks. tilføjede enheder pr. drab",
+ "mod_epicloot_config_rollbase": "Slagbasis",
+ "mod_epicloot_config_rollpervalue": "Slag pr. værdi",
+ "mod_epicloot_config_rollcurve": "Slagkurve",
+ "mod_epicloot_config_floorfraction": "Gulvandel",
+ "mod_epicloot_config_chitin": "Kitin",
+ "mod_epicloot_config_copperscrap": "Kobberskrot",
+ "mod_epicloot_config_ironscrap": "Skrotjern",
+ "mod_epicloot_config_runestoneepic": "Episk runesten",
+ "mod_epicloot_config_runestonelegendary": "Legendarisk runesten",
"mod_epicloot_me_modifylowhealth_config_max": "Maksimal tærskel for lavt helbred",
"mod_epicloot_me_riches_config_coins": "Møntværdi",
"mod_epicloot_me_riches_config_amber": "Ravværdi",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanent)",
"mod_epicloot_legendary_brokkrgift": "Legendarisk Brokkrs gave",
"mod_epicloot_mythic_brokkrgift": "Mytisk Brokkrs gave",
+ "mod_epicloot_ancient_brokkrgift": "Ældgammel Brokkrs gave",
"mod_epicloot_slotchisel_introduce": "En overordentlig mægtig artefakt, der siges at indsætte en skårplads i enhver magisk genstand.",
"mod_epicloot_slotchisel_hint": "Træk den hen på en magisk genstand for at tilføje en skårplads",
"mod_epicloot_slotchisel_confirm_title": "Tilføj skårplads?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Hver undvigelsesrulning fjerner {0} sekunder fra dine gift- og brandeffekter.",
"mod_epicloot_me_stormrider_display": "+{0} % bevægelsesfart i storme",
"mod_epicloot_me_stormrider_desc": "+{0} % bevægelsesfart mens en storm raser.",
- "mod_epicloot_me_shockingcharge_display": "Chokerende ladning",
- "mod_epicloot_me_shockingcharge_desc": "Dine træf i kamp opbygger ladninger. Ved fuld ladning udløser næste træf en fremadrettet lynchokbølge, der giver en del af skaden fra dine tidligere træf.",
- "mod_epicloot_me_forestsaid_display": "Skovens hjælp",
- "mod_epicloot_me_forestsaid_desc": "Når du bliver ramt, bryder snærende rødder frem og binder fjender i nærheden.",
- "mod_epicloot_me_corpserot_display": "Ligrådne",
- "mod_epicloot_me_corpserot_desc": "Fjender du dræber kan briste i en sky af gift og forgifte modstandere i nærheden.",
- "mod_epicloot_me_icyretribution_display": "Iskold gengældelse",
- "mod_epicloot_me_icyretribution_desc": "Når du bliver ramt, udløser du en frostnova omkring dig. Nedkølingen skalerer med sjældenheden.",
+ "mod_epicloot_me_shockingcharge_display": "Chokerende ladning: hver {0:0.#} træf",
+ "mod_epicloot_me_shockingcharge_desc": "For hver {0:0.#} træf i kamp udløser det næste træf en fremadrettet lynchokbølge, der giver {1:0.#}% af skaden fra de træf. Højere sjældenhed kræver færre træf.",
+ "mod_epicloot_me_forestsaid_display": "Skovens hjælp: {0:0.#} m, {1:0.#} s nedkøling",
+ "mod_epicloot_me_forestsaid_desc": "Når du bliver ramt, bryder snærende rødder frem og binder fjender inden for {0:0.#} meter. Højere sjældenhed rækker længere og lades op på {1:0.#} sekunder.",
+ "mod_epicloot_me_corpserot_display": "Ligrådne: {0:0.#} gift",
+ "mod_epicloot_me_corpserot_desc": "Fjender du dræber kan briste i en sky af gift og give modstandere i nærheden {0:0.#} giftskade.",
+ "mod_epicloot_me_icyretribution_display": "Iskold gengældelse: {0:0.#} frost",
+ "mod_epicloot_me_icyretribution_desc": "Når du bliver ramt, udløser du en frostnova omkring dig, der giver {0:0.#} frostskade. Højere sjældenhed rammer hårdere og lades hurtigere op.",
"mod_epicloot_me_meteorsummoner_display": "Meteortilkalder: {0}× træfskade",
"mod_epicloot_me_meteorsummoner_desc": "Hvert {1} våbentræf kalder dit næste træf en brændende meteor ned på målet, som giver {0}× det træfs skade som ild.",
- "mod_epicloot_me_everflow_display": "Dronningens evige strøm",
- "mod_epicloot_me_everflow_desc": "At dræbe et væsen giver en stabelbar effekt, der øger din helbreds-, udholdenheds- og eitrregeneration.",
- "mod_epicloot_me_necroticfire_display": "Nekrotisk ild",
- "mod_epicloot_me_necroticfire_desc": "Fjerner al fysisk skade fra dit våben og tilføjer hele beløbet som både ild- og giftskade.",
+ "mod_epicloot_me_everflow_display": "Dronningens evige strøm: +{0:0.#}% regeneration pr. stak",
+ "mod_epicloot_me_everflow_desc": "At dræbe et væsen giver en stabelbar effekt, der øger din helbreds-, udholdenheds- og eitrregeneration med {0:0.#}% pr. stak, op til {1:0.#} stakke.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotisk ild: +{0:0.#}% ild og gift",
+ "mod_epicloot_me_necroticfire_desc": "Fjerner al fysisk skade fra dit våben og tilføjer {0:0.#}% af den som ildskade og lige så meget som giftskade.",
"mod_epicloot_circlet": "cirkel",
"mod_epicloot_belt": "bælte",
"mod_epicloot_remains": "rester",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendarisk {0}",
"mod_epicloot_basicmythicnameformat": "Mytisk {0}",
+ "mod_epicloot_basicancientnameformat": "Ældgammel {0}",
"mod_epicloot_rare_prefix1": "Underlig",
"mod_epicloot_rare_prefix2": "Sjælden",
"mod_epicloot_rare_prefix3": "Kryptisk",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Episk",
"mod_epicloot_Legendary": "Legendarisk",
"mod_epicloot_Mythic": "Mytisk",
+ "mod_epicloot_Ancient": "Ældgammel",
"mod_epicloot_search_placeholder": "Søg...",
"mod_epicloot_no_active_adventures": "Ingen aktive eventyr",
"mod_epicloot_set_bonuses": "Sætbonusser",
diff --git a/EpicLoot/localizations/Dutch.json b/EpicLoot/localizations/Dutch.json
index 41fd0ccf3..2cf4bb7cc 100644
--- a/EpicLoot/localizations/Dutch.json
+++ b/EpicLoot/localizations/Dutch.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Vuursteen",
"mod_epicloot_config_serpentscale": "Zeeslangschub",
"mod_epicloot_config_triplechance": "Kans op drievoudige vangst",
- "mod_epicloot_config_secondspercent": "Seconden per 1 %",
+ "mod_epicloot_config_secondsperpercent": "Seconden per 1 %",
"mod_epicloot_config_procchance": "Activeringskans",
"mod_epicloot_config_bonusrollsmin": "Min. bonusbuitworpen",
"mod_epicloot_config_bonusrollsmax": "Max. bonusbuitworpen",
+ "mod_epicloot_config_cooldown": "Afkoeling (s)",
+ "mod_epicloot_config_basecooldown": "Basisafkoeling (s)",
+ "mod_epicloot_config_cooldownperrarity": "Afkoeling per zeldzaamheid (s)",
+ "mod_epicloot_config_cooldownpervalue": "Afkoeling per waarde (s)",
+ "mod_epicloot_config_buffduration": "Duur (s)",
+ "mod_epicloot_config_tickinterval": "Tikinterval (s)",
+ "mod_epicloot_config_radius": "Radius (m)",
+ "mod_epicloot_config_baseradius": "Basisradius (m)",
+ "mod_epicloot_config_radiuspertier": "Radius per waarde (m)",
+ "mod_epicloot_config_maxcharges": "Treffers tot opladen",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Treffers tot opladen (basis)",
+ "mod_epicloot_config_chargespervalue": "Bespaarde treffers per waarde",
+ "mod_epicloot_config_mincharges": "Min. treffers tot opladen",
+ "mod_epicloot_config_mincooldown": "Min. afkoeling (s)",
+ "mod_epicloot_config_damagepertrigger": "Schade per activering",
+ "mod_epicloot_config_eitrpertrigger": "Eitr per activering",
+ "mod_epicloot_config_damagemultiplier": "Schadevermenigvuldiger",
+ "mod_epicloot_config_damagefraction": "Schadefractie",
+ "mod_epicloot_config_damagepervalue": "Schade per waarde",
+ "mod_epicloot_config_frostpertier": "Vorstschade per waarde",
+ "mod_epicloot_config_poisonpertier": "Gifschade per waarde",
+ "mod_epicloot_config_spawnheight": "Verschijnhoogte (m)",
+ "mod_epicloot_config_spawnradius": "Verschijnradius (m)",
+ "mod_epicloot_config_spawninterval": "Verschijninterval (s)",
+ "mod_epicloot_config_mindistance": "Min. lanceerafstand (m)",
+ "mod_epicloot_config_maxdistance": "Max. lanceerafstand (m)",
+ "mod_epicloot_config_projectilespeed": "Projectielsnelheid (m/s)",
+ "mod_epicloot_config_explosionradius": "Explosieradius (m)",
+ "mod_epicloot_config_conelength": "Kegellengte (m)",
+ "mod_epicloot_config_conemaxwidth": "Kegelbreedte (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Negeervenster na ontlading (s)",
+ "mod_epicloot_config_minmovespeed": "Min. loopsnelheid",
+ "mod_epicloot_config_patchradius": "Plekradius (m)",
+ "mod_epicloot_config_patchlifetime": "Plekduur (s)",
+ "mod_epicloot_config_patchtickinterval": "Tikinterval van plek (s)",
+ "mod_epicloot_config_speedfloor": "Snelheidsondergrens",
+ "mod_epicloot_config_batlifetime": "Levensduur vleermuis (s)",
+ "mod_epicloot_config_summoncountoffset": "Verschuiving oproepaantal",
+ "mod_epicloot_config_concurrentpersummon": "Max. gelijktijdig per oproep",
+ "mod_epicloot_config_maxreduction": "Max. vermindering",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% max. gezondheid per waarde",
+ "mod_epicloot_config_minhealthreduction": "Min. gezondheidsvermindering",
+ "mod_epicloot_config_carryweightpervalue": "Draaggewicht per waarde",
+ "mod_epicloot_config_minresultingcarryweight": "Min. draaggewicht",
+ "mod_epicloot_config_weightthreshold": "Gewichtsdrempel",
+ "mod_epicloot_config_weightperbonus": "Gewicht per bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-kosten per schade",
+ "mod_epicloot_config_minskilllevel": "Min. vaardigheidsniveau",
+ "mod_epicloot_config_levelweightexponent": "Exponent voor niveaugewicht",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. niveaustijgingen per activering",
+ "mod_epicloot_config_maxmultiplier": "Max. buitvermenigvuldiger",
+ "mod_epicloot_config_maxunitsperentry": "Max. eenheden per vermelding",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. toegevoegde eenheden per kill",
+ "mod_epicloot_config_rollbase": "Worpbasis",
+ "mod_epicloot_config_rollpervalue": "Worp per waarde",
+ "mod_epicloot_config_rollcurve": "Worpcurve",
+ "mod_epicloot_config_floorfraction": "Ondergrensfractie",
+ "mod_epicloot_config_chitin": "Chitine",
+ "mod_epicloot_config_copperscrap": "Koperschroot",
+ "mod_epicloot_config_ironscrap": "IJzerschroot",
+ "mod_epicloot_config_runestoneepic": "Epische runensteen",
+ "mod_epicloot_config_runestonelegendary": "Legendarische runensteen",
"mod_epicloot_me_modifylowhealth_config_max": "Maximale drempel voor lage gezondheid",
"mod_epicloot_me_riches_config_coins": "Muntwaarde",
"mod_epicloot_me_riches_config_amber": "Barnsteenwaarde",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanent)",
"mod_epicloot_legendary_brokkrgift": "Legendarisch Brokkrs geschenk",
"mod_epicloot_mythic_brokkrgift": "Mythisch Brokkrs geschenk",
+ "mod_epicloot_ancient_brokkrgift": "Oeroud Brokkrs geschenk",
"mod_epicloot_slotchisel_introduce": "Een buitengewoon krachtig artefact dat naar verluidt een scherfsleuf in elk magisch voorwerp inbedt.",
"mod_epicloot_slotchisel_hint": "Sleep op een magisch voorwerp om een scherfsleuf toe te voegen",
"mod_epicloot_slotchisel_confirm_title": "Scherfsleuf toevoegen?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Elke ontwijkrol haalt {0} seconden van je gif- en brandeffecten af.",
"mod_epicloot_me_stormrider_display": "+{0} % loopsnelheid tijdens stormen",
"mod_epicloot_me_stormrider_desc": "+{0} % loopsnelheid terwijl er een storm woedt.",
- "mod_epicloot_me_shockingcharge_display": "Schokkende lading",
- "mod_epicloot_me_shockingcharge_desc": "Je gevechtstreffers bouwen ladingen op. Bij volle lading ontketent de volgende treffer een voorwaartse bliksemschokgolf die een deel van de schade van je vorige treffers toebrengt.",
- "mod_epicloot_me_forestsaid_display": "Hulp van het woud",
- "mod_epicloot_me_forestsaid_desc": "Wanneer je geraakt wordt, breken verstrikkende wortels los die vijanden in de buurt vastzetten.",
- "mod_epicloot_me_corpserot_display": "Lijkenrot",
- "mod_epicloot_me_corpserot_desc": "Vijanden die je doodt kunnen uiteenspatten in een wolk van gif en zo nabije tegenstanders vergiftigen.",
- "mod_epicloot_me_icyretribution_display": "IJzige vergelding",
- "mod_epicloot_me_icyretribution_desc": "Wanneer je geraakt wordt, laat je een vorstnova om je heen ontploffen. De afkoeltijd schaalt met de zeldzaamheid.",
+ "mod_epicloot_me_shockingcharge_display": "Schokkende lading: elke {0:0.#} treffers",
+ "mod_epicloot_me_shockingcharge_desc": "Elke {0:0.#} gevechtstreffers ontketent de volgende treffer een voorwaartse bliksemschokgolf die {1:0.#}% van de schade van die treffers toebrengt. Een hogere zeldzaamheid vergt minder treffers.",
+ "mod_epicloot_me_forestsaid_display": "Hulp van het woud: {0:0.#} m, {1:0.#} s afkoeling",
+ "mod_epicloot_me_forestsaid_desc": "Wanneer je geraakt wordt, breken verstrikkende wortels los die vijanden binnen {0:0.#} meter vastzetten. Een hogere zeldzaamheid reikt verder en laadt in {1:0.#} seconden op.",
+ "mod_epicloot_me_corpserot_display": "Lijkenrot: {0:0.#} gif",
+ "mod_epicloot_me_corpserot_desc": "Vijanden die je doodt kunnen uiteenspatten in een wolk van gif en nabije tegenstanders {0:0.#} gifschade toebrengen.",
+ "mod_epicloot_me_icyretribution_display": "IJzige vergelding: {0:0.#} vorst",
+ "mod_epicloot_me_icyretribution_desc": "Wanneer je geraakt wordt, laat je een vorstnova om je heen ontploffen die {0:0.#} vorstschade toebrengt. Een hogere zeldzaamheid slaat harder toe en laadt sneller op.",
"mod_epicloot_me_meteorsummoner_display": "Meteoroproeper: {0}× trefferschade",
"mod_epicloot_me_meteorsummoner_desc": "Elke {1} wapentreffers roept je volgende treffer een vurige meteoor op het doel op die {0}× de schade van die treffer als vuur toebrengt.",
- "mod_epicloot_me_everflow_display": "Eeuwige stroom van de Koningin",
- "mod_epicloot_me_everflow_desc": "Een wezen doden verleent een stapelbaar effect dat je gezondheids-, stamina- en eitrregeneratie verhoogt.",
- "mod_epicloot_me_necroticfire_display": "Necrotisch vuur",
- "mod_epicloot_me_necroticfire_desc": "Haalt alle fysieke schade van je wapen af en voegt dat volledige bedrag toe als zowel vuur- als gifschade.",
+ "mod_epicloot_me_everflow_display": "Eeuwige stroom van de Koningin: +{0:0.#}% regeneratie per stapel",
+ "mod_epicloot_me_everflow_desc": "Een wezen doden verleent een stapelbaar effect dat je gezondheids-, stamina- en eitrregeneratie met {0:0.#}% per stapel verhoogt, tot {1:0.#} stapels.",
+ "mod_epicloot_me_necroticfire_display": "Necrotisch vuur: +{0:0.#}% vuur en gif",
+ "mod_epicloot_me_necroticfire_desc": "Haalt alle fysieke schade van je wapen af en voegt {0:0.#}% daarvan toe als vuurschade en evenveel als gifschade.",
"mod_epicloot_circlet": "hoofdband",
"mod_epicloot_belt": "riem",
"mod_epicloot_remains": "overblijfselen",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendary {0}",
"mod_epicloot_basicmythicnameformat": "Mythic {0}",
+ "mod_epicloot_basicancientnameformat": "Oeroud {0}",
"mod_epicloot_rare_prefix1": "Vreemd",
"mod_epicloot_rare_prefix2": "Zeldzaam",
"mod_epicloot_rare_prefix3": "Cryptisch",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epos",
"mod_epicloot_Legendary": "Legendarisch",
"mod_epicloot_Mythic": " Mythisch",
+ "mod_epicloot_Ancient": "Oeroud",
"mod_epicloot_search_placeholder": "Zoeken...",
"mod_epicloot_no_active_adventures": "Geen actieve avonturen",
"mod_epicloot_set_bonuses": "Setbonussen",
diff --git a/EpicLoot/localizations/English.json b/EpicLoot/localizations/English.json
index c16e182a6..d0a184a5c 100644
--- a/EpicLoot/localizations/English.json
+++ b/EpicLoot/localizations/English.json
@@ -70,6 +70,10 @@
"mod_epicloot_config_baseradius": "Base Radius (m)",
"mod_epicloot_config_radiuspertier": "Radius per Value (m)",
"mod_epicloot_config_maxcharges": "Hits to Charge",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Hits to Charge (Base)",
+ "mod_epicloot_config_chargespervalue": "Hits Saved per Value",
+ "mod_epicloot_config_mincharges": "Min Hits to Charge",
+ "mod_epicloot_config_mincooldown": "Min Cooldown (s)",
"mod_epicloot_config_damagepertrigger": "Damage per Trigger",
"mod_epicloot_config_eitrpertrigger": "Eitr per Trigger",
"mod_epicloot_config_damagemultiplier": "Damage Multiplier",
@@ -656,20 +660,20 @@
"mod_epicloot_me_stormrider_display": "+{0}% Movement Speed in Storms",
"mod_epicloot_me_stormrider_desc": "+{0}% movement speed while a storm rages.",
- "mod_epicloot_me_shockingcharge_display": "Shocking Charge",
- "mod_epicloot_me_shockingcharge_desc": "Your combat hits build charges. At max charge, the next hit unleashes a forward lightning shockwave that deals a portion of your previous hits' damage.",
- "mod_epicloot_me_forestsaid_display": "Forest's Aid",
- "mod_epicloot_me_forestsaid_desc": "When you are hit, ensnaring roots erupt and immobilize nearby enemies.",
- "mod_epicloot_me_corpserot_display": "Corpse Rot",
- "mod_epicloot_me_corpserot_desc": "Enemies you kill can burst in a cloud of venom, poisoning nearby foes.",
- "mod_epicloot_me_icyretribution_display": "Icy Retribution",
- "mod_epicloot_me_icyretribution_desc": "When you are hit, detonate a frost nova around you. Cooldown scales with rarity.",
+ "mod_epicloot_me_shockingcharge_display": "Shocking Charge: Every {0:0.#} Hits",
+ "mod_epicloot_me_shockingcharge_desc": "Every {0:0.#} combat hits, the next hit unleashes a forward lightning shockwave dealing {1:0.#}% of the damage those hits did. A higher rarity shard needs fewer hits.",
+ "mod_epicloot_me_forestsaid_display": "Forest's Aid: {0:0.#}m, {1:0.#}s Cooldown",
+ "mod_epicloot_me_forestsaid_desc": "When you are hit, ensnaring roots erupt and immobilize enemies within {0:0.#} meters. A higher rarity shard reaches further and recharges in {1:0.#} seconds.",
+ "mod_epicloot_me_corpserot_display": "Corpse Rot: {0:0.#} Poison",
+ "mod_epicloot_me_corpserot_desc": "Enemies you kill can burst in a cloud of venom, dealing {0:0.#} poison damage to nearby foes.",
+ "mod_epicloot_me_icyretribution_display": "Icy Retribution: {0:0.#} Frost",
+ "mod_epicloot_me_icyretribution_desc": "When you are hit, detonate a frost nova around you dealing {0:0.#} frost damage. A higher rarity shard hits harder and recharges sooner.",
"mod_epicloot_me_meteorsummoner_display": "Meteor Summoner: {0}x Hit Damage",
"mod_epicloot_me_meteorsummoner_desc": "Every {1} weapon hits, your next hit calls down a fiery meteor on the target that deals {0}x that hit's damage as fire.",
- "mod_epicloot_me_everflow_display": "Queen's Everflow",
- "mod_epicloot_me_everflow_desc": "Killing a creature grants a stacking buff that boosts your health, stamina and eitr regeneration.",
- "mod_epicloot_me_necroticfire_display": "Necrotic Fire",
- "mod_epicloot_me_necroticfire_desc": "Strips all physical damage from your weapon and adds that full amount as both fire and poison damage.",
+ "mod_epicloot_me_everflow_display": "Queen's Everflow: +{0:0.#}% Regen per Stack",
+ "mod_epicloot_me_everflow_desc": "Killing a creature grants a stacking buff that boosts your health, stamina and eitr regeneration by {0:0.#}% per stack, up to {1:0.#} stacks.",
+ "mod_epicloot_me_necroticfire_display": "Necrotic Fire: +{0:0.#}% Fire and Poison",
+ "mod_epicloot_me_necroticfire_desc": "Strips all physical damage from your weapon and adds {0:0.#}% of it back as fire damage, and the same again as poison damage.",
"mod_epicloot_circlet": "circlet",
@@ -851,6 +855,7 @@
"mod_epicloot_enchanting_select": "Select",
"mod_epicloot_enchanting_sorting": "Change Sorting",
"mod_epicloot_enchanting_changequantity": "Change Quantity",
+ "mod_epicloot_enchanting_identifycategory": "Change Category",
"mod_epicloot_enchanting_scroll": "Scroll Info",
"mod_epicloot_active_magic_effects": "Active Magic Effects",
diff --git a/EpicLoot/localizations/Finnish.json b/EpicLoot/localizations/Finnish.json
index 83797f136..2ab307589 100644
--- a/EpicLoot/localizations/Finnish.json
+++ b/EpicLoot/localizations/Finnish.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Piikivi",
"mod_epicloot_config_serpentscale": "Merikäärmeen suomu",
"mod_epicloot_config_triplechance": "Kolminkertaisen saaliin mahdollisuus",
- "mod_epicloot_config_secondspercent": "Sekuntia per 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekuntia per 1 %",
"mod_epicloot_config_procchance": "Laukeamistodennäköisyys",
"mod_epicloot_config_bonusrollsmin": "Vähimmäisbonusheitot saaliille",
"mod_epicloot_config_bonusrollsmax": "Enimmäisbonusheitot saaliille",
+ "mod_epicloot_config_cooldown": "Jäähtymisaika (s)",
+ "mod_epicloot_config_basecooldown": "Perusjäähtymisaika (s)",
+ "mod_epicloot_config_cooldownperrarity": "Jäähtymisaika per harvinaisuus (s)",
+ "mod_epicloot_config_cooldownpervalue": "Jäähtymisaika per arvo (s)",
+ "mod_epicloot_config_buffduration": "Kesto (s)",
+ "mod_epicloot_config_tickinterval": "Tikkiväli (s)",
+ "mod_epicloot_config_radius": "Säde (m)",
+ "mod_epicloot_config_baseradius": "Perussäde (m)",
+ "mod_epicloot_config_radiuspertier": "Säde per arvo (m)",
+ "mod_epicloot_config_maxcharges": "Osumia latautumiseen",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Osumia latautumiseen (perus)",
+ "mod_epicloot_config_chargespervalue": "Säästetyt osumat per arvo",
+ "mod_epicloot_config_mincharges": "Väh. osumia latautumiseen",
+ "mod_epicloot_config_mincooldown": "Väh. jäähtymisaika (s)",
+ "mod_epicloot_config_damagepertrigger": "Vahinko per laukaisu",
+ "mod_epicloot_config_eitrpertrigger": "Eitr per laukaisu",
+ "mod_epicloot_config_damagemultiplier": "Vahinkokerroin",
+ "mod_epicloot_config_damagefraction": "Vahinko-osuus",
+ "mod_epicloot_config_damagepervalue": "Vahinko per arvo",
+ "mod_epicloot_config_frostpertier": "Pakkasvahinko per arvo",
+ "mod_epicloot_config_poisonpertier": "Myrkkyvahinko per arvo",
+ "mod_epicloot_config_spawnheight": "Ilmestymiskorkeus (m)",
+ "mod_epicloot_config_spawnradius": "Ilmestymissäde (m)",
+ "mod_epicloot_config_spawninterval": "Ilmestymisväli (s)",
+ "mod_epicloot_config_mindistance": "Väh. laukaisuetäisyys (m)",
+ "mod_epicloot_config_maxdistance": "Enint. laukaisuetäisyys (m)",
+ "mod_epicloot_config_projectilespeed": "Ammuksen nopeus (m/s)",
+ "mod_epicloot_config_explosionradius": "Räjähdyssäde (m)",
+ "mod_epicloot_config_conelength": "Kartion pituus (m)",
+ "mod_epicloot_config_conemaxwidth": "Kartion leveys (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Purkauksen ohitusikkuna (s)",
+ "mod_epicloot_config_minmovespeed": "Väh. liikkumisnopeus",
+ "mod_epicloot_config_patchradius": "Alueen säde (m)",
+ "mod_epicloot_config_patchlifetime": "Alueen kesto (s)",
+ "mod_epicloot_config_patchtickinterval": "Alueen tikkiväli (s)",
+ "mod_epicloot_config_speedfloor": "Nopeuden alaraja",
+ "mod_epicloot_config_batlifetime": "Lepakon kesto (s)",
+ "mod_epicloot_config_summoncountoffset": "Kutsuttujen määrän siirtymä",
+ "mod_epicloot_config_concurrentpersummon": "Enint. samanaikaisia per kutsu",
+ "mod_epicloot_config_maxreduction": "Enimmäisvähennys",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% enimmäisterveydestä per arvo",
+ "mod_epicloot_config_minhealthreduction": "Väh. terveyden vähennys",
+ "mod_epicloot_config_carryweightpervalue": "Kantokyky per arvo",
+ "mod_epicloot_config_minresultingcarryweight": "Väh. kantokyky",
+ "mod_epicloot_config_weightthreshold": "Painoraja",
+ "mod_epicloot_config_weightperbonus": "Paino per bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-kustannus per vahinko",
+ "mod_epicloot_config_minskilllevel": "Väh. taitotaso",
+ "mod_epicloot_config_levelweightexponent": "Tason painotuseksponentti",
+ "mod_epicloot_config_maxlevelupsperproc": "Enint. tasonnousuja per laukaisu",
+ "mod_epicloot_config_maxmultiplier": "Enimmäissaaliskerroin",
+ "mod_epicloot_config_maxunitsperentry": "Enint. yksiköitä per rivi",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Enint. lisättyjä yksiköitä per tappo",
+ "mod_epicloot_config_rollbase": "Heiton perusta",
+ "mod_epicloot_config_rollpervalue": "Heitto per arvo",
+ "mod_epicloot_config_rollcurve": "Heiton käyrä",
+ "mod_epicloot_config_floorfraction": "Alarajan osuus",
+ "mod_epicloot_config_chitin": "Kitiini",
+ "mod_epicloot_config_copperscrap": "Jämäkupari",
+ "mod_epicloot_config_ironscrap": "Romurauta",
+ "mod_epicloot_config_runestoneepic": "Mahtava runokivi",
+ "mod_epicloot_config_runestonelegendary": "Legendaarinen runokivi",
"mod_epicloot_me_modifylowhealth_config_max": "Matalan terveyden enimmäisraja",
"mod_epicloot_me_riches_config_coins": "Kolikoiden arvo",
"mod_epicloot_me_riches_config_amber": "Meripihkan arvo",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(pysyvä)",
"mod_epicloot_legendary_brokkrgift": "Legendaarinen Brokkrin lahja",
"mod_epicloot_mythic_brokkrgift": "Myyttinen Brokkrin lahja",
+ "mod_epicloot_ancient_brokkrgift": "Muinainen Brokkrin lahja",
"mod_epicloot_slotchisel_introduce": "Äärimmäisen mahtava artefakti, jonka sanotaan upottavan sirupaikan mihin tahansa taikaesineeseen.",
"mod_epicloot_slotchisel_hint": "Vedä taikaesineen päälle lisätäksesi sirupaikan",
"mod_epicloot_slotchisel_confirm_title": "Lisätäänkö sirupaikka?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Jokainen väistökierähdys poistaa {0} sekuntia myrkky- ja palovaikutuksistasi.",
"mod_epicloot_me_stormrider_display": "+{0} % liikkumisnopeutta myrskyissä",
"mod_epicloot_me_stormrider_desc": "+{0} % liikkumisnopeutta myrskyn raivotessa.",
- "mod_epicloot_me_shockingcharge_display": "Sähköistävä lataus",
- "mod_epicloot_me_shockingcharge_desc": "Taisteluosumasi keräävät latauksia. Täydellä latauksella seuraava osuma vapauttaa eteenpäin suuntautuvan salamashokkiaallon, joka aiheuttaa osan aiempien osumiesi vahingosta.",
- "mod_epicloot_me_forestsaid_display": "Metsän apu",
- "mod_epicloot_me_forestsaid_desc": "Kun sinuun osutaan, kietovat juuret puhkeavat esiin ja pidättelevät lähellä olevia vihollisia.",
- "mod_epicloot_me_corpserot_display": "Raadonmätä",
- "mod_epicloot_me_corpserot_desc": "Tappamasi viholliset voivat purkautua myrkkypilveksi ja myrkyttää lähellä olevat vastustajat.",
- "mod_epicloot_me_icyretribution_display": "Jäinen kosto",
- "mod_epicloot_me_icyretribution_desc": "Kun sinuun osutaan, laukaiset ympärillesi pakkasnovan. Jäähtymisaika skaalautuu harvinaisuuden mukaan.",
+ "mod_epicloot_me_shockingcharge_display": "Sähköistävä lataus: joka {0:0.#} osuma",
+ "mod_epicloot_me_shockingcharge_desc": "Joka {0:0.#} taisteluosuman jälkeen seuraava osuma vapauttaa eteenpäin suuntautuvan salamashokkiaallon, joka aiheuttaa {1:0.#} % noiden osumien vahingosta. Korkeampi harvinaisuus vaatii vähemmän osumia.",
+ "mod_epicloot_me_forestsaid_display": "Metsän apu: {0:0.#} m, {1:0.#} s lataus",
+ "mod_epicloot_me_forestsaid_desc": "Kun sinuun osutaan, kietovat juuret puhkeavat esiin ja pidättelevät vihollisia {0:0.#} metrin säteellä. Korkeampi harvinaisuus yltää kauemmas ja latautuu {1:0.#} sekunnissa.",
+ "mod_epicloot_me_corpserot_display": "Raadonmätä: {0:0.#} myrkkyä",
+ "mod_epicloot_me_corpserot_desc": "Tappamasi viholliset voivat purkautua myrkkypilveksi ja aiheuttaa lähellä oleville vastustajille {0:0.#} myrkkyvahinkoa.",
+ "mod_epicloot_me_icyretribution_display": "Jäinen kosto: {0:0.#} pakkasta",
+ "mod_epicloot_me_icyretribution_desc": "Kun sinuun osutaan, laukaiset ympärillesi pakkasnovan, joka aiheuttaa {0:0.#} pakkasvahinkoa. Korkeampi harvinaisuus iskee kovempaa ja latautuu nopeammin.",
"mod_epicloot_me_meteorsummoner_display": "Meteorinkutsuja: {0}× osumavahinko",
"mod_epicloot_me_meteorsummoner_desc": "Joka {1} aseosuman jälkeen seuraava osumasi kutsuu tulisen meteorin kohteeseen, joka aiheuttaa {0}× kyseisen osuman vahingon tulena.",
- "mod_epicloot_me_everflow_display": "Kuningattaren ikuinen virta",
- "mod_epicloot_me_everflow_desc": "Olennon tappaminen antaa pinoutuvan vaikutuksen, joka parantaa terveyden, kestävyyden ja eitrin palautumista.",
- "mod_epicloot_me_necroticfire_display": "Nekroottinen tuli",
- "mod_epicloot_me_necroticfire_desc": "Poistaa aseestasi kaiken fyysisen vahingon ja lisää saman määrän sekä tuli- että myrkkyvahinkona.",
+ "mod_epicloot_me_everflow_display": "Kuningattaren ikuinen virta: +{0:0.#} % palautumista per pino",
+ "mod_epicloot_me_everflow_desc": "Olennon tappaminen antaa pinoutuvan vaikutuksen, joka parantaa terveyden, kestävyyden ja eitrin palautumista {0:0.#} % per pino, enintään {1:0.#} pinoa.",
+ "mod_epicloot_me_necroticfire_display": "Nekroottinen tuli: +{0:0.#} % tulta ja myrkkyä",
+ "mod_epicloot_me_necroticfire_desc": "Poistaa aseestasi kaiken fyysisen vahingon ja lisää siitä {0:0.#} % tulivahinkona ja saman verran myrkkyvahinkona.",
"mod_epicloot_circlet": "rengas",
"mod_epicloot_belt": "vyö",
"mod_epicloot_remains": "jäännökset",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendaarinen {0}",
"mod_epicloot_basicmythicnameformat": "Myyttinen {0}",
+ "mod_epicloot_basicancientnameformat": "Muinainen {0}",
"mod_epicloot_rare_prefix1": "Outo",
"mod_epicloot_rare_prefix2": "Harvinainen",
"mod_epicloot_rare_prefix3": "Salaperäinen",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Mahtava",
"mod_epicloot_Legendary": "Legendaarinen",
"mod_epicloot_Mythic": "Myyttinen",
+ "mod_epicloot_Ancient": "Muinainen",
"mod_epicloot_search_placeholder": "Haku...",
"mod_epicloot_no_active_adventures": "Ei aktiivisia seikkailuja",
"mod_epicloot_set_bonuses": "Settiponukset",
diff --git a/EpicLoot/localizations/French.json b/EpicLoot/localizations/French.json
index 24bf758b6..06fab1ca8 100644
--- a/EpicLoot/localizations/French.json
+++ b/EpicLoot/localizations/French.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Silex",
"mod_epicloot_config_serpentscale": "Écaille de serpent",
"mod_epicloot_config_triplechance": "Chance de triple prise",
- "mod_epicloot_config_secondspercent": "Secondes par 1 %",
+ "mod_epicloot_config_secondsperpercent": "Secondes par 1 %",
"mod_epicloot_config_procchance": "Chance de déclenchement",
"mod_epicloot_config_bonusrollsmin": "Tirages de butin bonus min.",
"mod_epicloot_config_bonusrollsmax": "Tirages de butin bonus max.",
+ "mod_epicloot_config_cooldown": "Recharge (s)",
+ "mod_epicloot_config_basecooldown": "Recharge de base (s)",
+ "mod_epicloot_config_cooldownperrarity": "Recharge par rareté (s)",
+ "mod_epicloot_config_cooldownpervalue": "Recharge par valeur (s)",
+ "mod_epicloot_config_buffduration": "Durée (s)",
+ "mod_epicloot_config_tickinterval": "Intervalle de tick (s)",
+ "mod_epicloot_config_radius": "Rayon (m)",
+ "mod_epicloot_config_baseradius": "Rayon de base (m)",
+ "mod_epicloot_config_radiuspertier": "Rayon par valeur (m)",
+ "mod_epicloot_config_maxcharges": "Coups avant charge",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Coups avant charge (base)",
+ "mod_epicloot_config_chargespervalue": "Coups économisés par valeur",
+ "mod_epicloot_config_mincharges": "Coups min. avant charge",
+ "mod_epicloot_config_mincooldown": "Recharge min. (s)",
+ "mod_epicloot_config_damagepertrigger": "Dégâts par déclenchement",
+ "mod_epicloot_config_eitrpertrigger": "Eitr par déclenchement",
+ "mod_epicloot_config_damagemultiplier": "Multiplicateur de dégâts",
+ "mod_epicloot_config_damagefraction": "Fraction des dégâts",
+ "mod_epicloot_config_damagepervalue": "Dégâts par valeur",
+ "mod_epicloot_config_frostpertier": "Dégâts de givre par valeur",
+ "mod_epicloot_config_poisonpertier": "Dégâts de poison par valeur",
+ "mod_epicloot_config_spawnheight": "Hauteur d'apparition (m)",
+ "mod_epicloot_config_spawnradius": "Rayon d'apparition (m)",
+ "mod_epicloot_config_spawninterval": "Intervalle d'apparition (s)",
+ "mod_epicloot_config_mindistance": "Distance de lancement min. (m)",
+ "mod_epicloot_config_maxdistance": "Distance de lancement max. (m)",
+ "mod_epicloot_config_projectilespeed": "Vitesse du projectile (m/s)",
+ "mod_epicloot_config_explosionradius": "Rayon d'explosion (m)",
+ "mod_epicloot_config_conelength": "Longueur du cône (m)",
+ "mod_epicloot_config_conemaxwidth": "Largeur du cône (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Fenêtre d'ignorance de décharge (s)",
+ "mod_epicloot_config_minmovespeed": "Vitesse de déplacement min.",
+ "mod_epicloot_config_patchradius": "Rayon de la zone (m)",
+ "mod_epicloot_config_patchlifetime": "Durée de la zone (s)",
+ "mod_epicloot_config_patchtickinterval": "Intervalle de tick de la zone (s)",
+ "mod_epicloot_config_speedfloor": "Vitesse plancher",
+ "mod_epicloot_config_batlifetime": "Durée de la chauve-souris (s)",
+ "mod_epicloot_config_summoncountoffset": "Décalage du nombre d'invocations",
+ "mod_epicloot_config_concurrentpersummon": "Max. simultanés par invocation",
+ "mod_epicloot_config_maxreduction": "Réduction max.",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% de santé max. par valeur",
+ "mod_epicloot_config_minhealthreduction": "Réduction de santé min.",
+ "mod_epicloot_config_carryweightpervalue": "Capacité de charge par valeur",
+ "mod_epicloot_config_minresultingcarryweight": "Capacité de charge min.",
+ "mod_epicloot_config_weightthreshold": "Seuil de poids",
+ "mod_epicloot_config_weightperbonus": "Poids par bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Coût en eitr par dégât",
+ "mod_epicloot_config_minskilllevel": "Niveau de compétence min.",
+ "mod_epicloot_config_levelweightexponent": "Exposant de pondération du niveau",
+ "mod_epicloot_config_maxlevelupsperproc": "Gains de niveau max. par déclenchement",
+ "mod_epicloot_config_maxmultiplier": "Multiplicateur de butin max.",
+ "mod_epicloot_config_maxunitsperentry": "Unités max. par entrée",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Unités ajoutées max. par mise à mort",
+ "mod_epicloot_config_rollbase": "Base du tirage",
+ "mod_epicloot_config_rollpervalue": "Tirage par valeur",
+ "mod_epicloot_config_rollcurve": "Courbe du tirage",
+ "mod_epicloot_config_floorfraction": "Fraction plancher",
+ "mod_epicloot_config_chitin": "Chitine",
+ "mod_epicloot_config_copperscrap": "Morceau de cuivre",
+ "mod_epicloot_config_ironscrap": "Ferraille",
+ "mod_epicloot_config_runestoneepic": "Pierre runique épique",
+ "mod_epicloot_config_runestonelegendary": "Pierre runique légendaire",
"mod_epicloot_me_modifylowhealth_config_max": "Seuil maximal de santé basse",
"mod_epicloot_me_riches_config_coins": "Valeur des pièces",
"mod_epicloot_me_riches_config_amber": "Valeur de l'ambre",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanent)",
"mod_epicloot_legendary_brokkrgift": "Don de Brokkr légendaire",
"mod_epicloot_mythic_brokkrgift": "Don de Brokkr mythique",
+ "mod_epicloot_ancient_brokkrgift": "Don de Brokkr ancien",
"mod_epicloot_slotchisel_introduce": "Un artefact d'une puissance extrême, réputé pour sertir un emplacement d'éclat dans n'importe quel objet magique.",
"mod_epicloot_slotchisel_hint": "Faites-le glisser sur un objet magique pour ajouter un emplacement d'éclat",
"mod_epicloot_slotchisel_confirm_title": "Ajouter un emplacement d'éclat ?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Chaque roulade d'esquive retire {0} secondes à vos effets de poison et de brûlure.",
"mod_epicloot_me_stormrider_display": "+{0} % de vitesse de déplacement pendant les tempêtes",
"mod_epicloot_me_stormrider_desc": "+{0} % de vitesse de déplacement tant qu'une tempête fait rage.",
- "mod_epicloot_me_shockingcharge_display": "Charge foudroyante",
- "mod_epicloot_me_shockingcharge_desc": "Vos coups au combat accumulent des charges. À charge maximale, le coup suivant libère une onde de choc électrique vers l'avant qui inflige une partie des dégâts de vos coups précédents.",
- "mod_epicloot_me_forestsaid_display": "Aide de la forêt",
- "mod_epicloot_me_forestsaid_desc": "Quand vous êtes touché, des racines jaillissent et immobilisent les ennemis proches.",
- "mod_epicloot_me_corpserot_display": "Pourriture cadavérique",
- "mod_epicloot_me_corpserot_desc": "Les ennemis que vous tuez peuvent éclater en un nuage de venin, empoisonnant les adversaires proches.",
- "mod_epicloot_me_icyretribution_display": "Rétribution glaciale",
- "mod_epicloot_me_icyretribution_desc": "Quand vous êtes touché, déclenche une nova de givre autour de vous. Le temps de recharge dépend de la rareté.",
+ "mod_epicloot_me_shockingcharge_display": "Charge foudroyante : tous les {0:0.#} coups",
+ "mod_epicloot_me_shockingcharge_desc": "Tous les {0:0.#} coups au combat, le coup suivant libère une onde de choc électrique vers l'avant qui inflige {1:0.#} % des dégâts de ces coups. Une rareté supérieure demande moins de coups.",
+ "mod_epicloot_me_forestsaid_display": "Aide de la forêt : {0:0.#} m, {1:0.#} s de recharge",
+ "mod_epicloot_me_forestsaid_desc": "Quand vous êtes touché, des racines jaillissent et immobilisent les ennemis dans un rayon de {0:0.#} mètres. Une rareté supérieure porte plus loin et se recharge en {1:0.#} secondes.",
+ "mod_epicloot_me_corpserot_display": "Pourriture cadavérique : {0:0.#} poison",
+ "mod_epicloot_me_corpserot_desc": "Les ennemis que vous tuez peuvent éclater en un nuage de venin, infligeant {0:0.#} dégâts de poison aux adversaires proches.",
+ "mod_epicloot_me_icyretribution_display": "Rétribution glaciale : {0:0.#} givre",
+ "mod_epicloot_me_icyretribution_desc": "Quand vous êtes touché, déclenche une nova de givre autour de vous qui inflige {0:0.#} dégâts de givre. Une rareté supérieure frappe plus fort et se recharge plus vite.",
"mod_epicloot_me_meteorsummoner_display": "Invocateur de météore : ×{0} des dégâts du coup",
"mod_epicloot_me_meteorsummoner_desc": "Tous les {1} coups d'arme, votre coup suivant fait s'abattre un météore enflammé sur la cible, infligeant {0} fois les dégâts de ce coup en feu.",
- "mod_epicloot_me_everflow_display": "Flux éternel de la Reine",
- "mod_epicloot_me_everflow_desc": "Tuer une créature accorde un effet cumulable qui améliore votre régénération de santé, d'endurance et d'eitr.",
- "mod_epicloot_me_necroticfire_display": "Feu nécrotique",
- "mod_epicloot_me_necroticfire_desc": "Retire tous les dégâts physiques de votre arme et ajoute ce montant complet à la fois en dégâts de feu et de poison.",
+ "mod_epicloot_me_everflow_display": "Flux éternel de la Reine : +{0:0.#} % de régénération par cumul",
+ "mod_epicloot_me_everflow_desc": "Tuer une créature accorde un effet cumulable qui améliore votre régénération de santé, d'endurance et d'eitr de {0:0.#} % par cumul, jusqu'à {1:0.#} cumuls.",
+ "mod_epicloot_me_necroticfire_display": "Feu nécrotique : +{0:0.#} % de feu et de poison",
+ "mod_epicloot_me_necroticfire_desc": "Retire tous les dégâts physiques de votre arme et en ajoute {0:0.#} % en dégâts de feu, puis autant en dégâts de poison.",
"mod_epicloot_circlet": "diadème",
"mod_epicloot_belt": "ceinture",
"mod_epicloot_remains": "restes",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Légendaire {0}",
"mod_epicloot_basicmythicnameformat": "Mythique {0}",
+ "mod_epicloot_basicancientnameformat": "Ancien {0}",
"mod_epicloot_rare_prefix1": "Étrange",
"mod_epicloot_rare_prefix2": "Rare",
"mod_epicloot_rare_prefix3": "Cryptique",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Épique",
"mod_epicloot_Legendary": "Légendaire",
"mod_epicloot_Mythic": "Mythique",
+ "mod_epicloot_Ancient": "Ancien",
"mod_epicloot_search_placeholder": "Rechercher...",
"mod_epicloot_no_active_adventures": "Aucune aventure en cours",
"mod_epicloot_set_bonuses": "Bonus de set",
diff --git a/EpicLoot/localizations/Georgian.json b/EpicLoot/localizations/Georgian.json
index ca78557e6..a194cc772 100644
--- a/EpicLoot/localizations/Georgian.json
+++ b/EpicLoot/localizations/Georgian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "კაჟი",
"mod_epicloot_config_serpentscale": "გველის ქერცლი",
"mod_epicloot_config_triplechance": "სამმაგი ნადავლის შანსი",
- "mod_epicloot_config_secondspercent": "წამი 1%-ზე",
+ "mod_epicloot_config_secondsperpercent": "წამი 1%-ზე",
"mod_epicloot_config_procchance": "ამოქმედების შანსი",
"mod_epicloot_config_bonusrollsmin": "ბონუს ნადავლის მინიმალური გათამაშება",
"mod_epicloot_config_bonusrollsmax": "ბონუს ნადავლის მაქსიმალური გათამაშება",
+ "mod_epicloot_config_cooldown": "დაყოვნება (წმ)",
+ "mod_epicloot_config_basecooldown": "საბაზისო დაყოვნება (წმ)",
+ "mod_epicloot_config_cooldownperrarity": "დაყოვნება რარიტეტზე (წმ)",
+ "mod_epicloot_config_cooldownpervalue": "დაყოვნება მნიშვნელობაზე (წმ)",
+ "mod_epicloot_config_buffduration": "ხანგრძლივობა (წმ)",
+ "mod_epicloot_config_tickinterval": "ტიკის ინტერვალი (წმ)",
+ "mod_epicloot_config_radius": "რადიუსი (მ)",
+ "mod_epicloot_config_baseradius": "საბაზისო რადიუსი (მ)",
+ "mod_epicloot_config_radiuspertier": "რადიუსი მნიშვნელობაზე (მ)",
+ "mod_epicloot_config_maxcharges": "დარტყმა დამუხტვამდე",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "დარტყმა დამუხტვამდე (საბაზისო)",
+ "mod_epicloot_config_chargespervalue": "დაზოგილი დარტყმა მნიშვნელობაზე",
+ "mod_epicloot_config_mincharges": "მინ. დარტყმა დამუხტვამდე",
+ "mod_epicloot_config_mincooldown": "მინ. დაყოვნება (წმ)",
+ "mod_epicloot_config_damagepertrigger": "ზიანი გააქტიურებაზე",
+ "mod_epicloot_config_eitrpertrigger": "ეიტრი გააქტიურებაზე",
+ "mod_epicloot_config_damagemultiplier": "ზიანის მამრავლი",
+ "mod_epicloot_config_damagefraction": "ზიანის წილი",
+ "mod_epicloot_config_damagepervalue": "ზიანი მნიშვნელობაზე",
+ "mod_epicloot_config_frostpertier": "ყინვის ზიანი მნიშვნელობაზე",
+ "mod_epicloot_config_poisonpertier": "შხამის ზიანი მნიშვნელობაზე",
+ "mod_epicloot_config_spawnheight": "გაჩენის სიმაღლე (მ)",
+ "mod_epicloot_config_spawnradius": "გაჩენის რადიუსი (მ)",
+ "mod_epicloot_config_spawninterval": "გაჩენის ინტერვალი (წმ)",
+ "mod_epicloot_config_mindistance": "მინ. გასროლის მანძილი (მ)",
+ "mod_epicloot_config_maxdistance": "მაქს. გასროლის მანძილი (მ)",
+ "mod_epicloot_config_projectilespeed": "ჭურვის სიჩქარე (მ/წმ)",
+ "mod_epicloot_config_explosionradius": "აფეთქების რადიუსი (მ)",
+ "mod_epicloot_config_conelength": "კონუსის სიგრძე (მ)",
+ "mod_epicloot_config_conemaxwidth": "კონუსის სიგანე (მ)",
+ "mod_epicloot_config_dischargeignorewindow": "განმუხტვის იგნორირების ფანჯარა (წმ)",
+ "mod_epicloot_config_minmovespeed": "მინ. მოძრაობის სიჩქარე",
+ "mod_epicloot_config_patchradius": "ლაქის რადიუსი (მ)",
+ "mod_epicloot_config_patchlifetime": "ლაქის ხანგრძლივობა (წმ)",
+ "mod_epicloot_config_patchtickinterval": "ლაქის ტიკის ინტერვალი (წმ)",
+ "mod_epicloot_config_speedfloor": "სიჩქარის ქვედა ზღვარი",
+ "mod_epicloot_config_batlifetime": "ღამურის ხანგრძლივობა (წმ)",
+ "mod_epicloot_config_summoncountoffset": "მოხმობის რაოდენობის წანაცვლება",
+ "mod_epicloot_config_concurrentpersummon": "მაქს. ერთდროულად მოხმობაზე",
+ "mod_epicloot_config_maxreduction": "მაქს. შემცირება",
+ "mod_epicloot_config_maxhealthpercentpervalue": "მაქს. ჯანმრთელობის % მნიშვნელობაზე",
+ "mod_epicloot_config_minhealthreduction": "მინ. ჯანმრთელობის შემცირება",
+ "mod_epicloot_config_carryweightpervalue": "ტარების წონა მნიშვნელობაზე",
+ "mod_epicloot_config_minresultingcarryweight": "მინ. ტარების წონა",
+ "mod_epicloot_config_weightthreshold": "წონის ზღვარი",
+ "mod_epicloot_config_weightperbonus": "წონა ბონუსზე",
+ "mod_epicloot_config_eitrcostperdamage": "ეიტრის ხარჯი ზიანზე",
+ "mod_epicloot_config_minskilllevel": "მინ. უნარის დონე",
+ "mod_epicloot_config_levelweightexponent": "დონის წონის მაჩვენებელი",
+ "mod_epicloot_config_maxlevelupsperproc": "მაქს. დონის მატება გააქტიურებაზე",
+ "mod_epicloot_config_maxmultiplier": "მაქს. ნადავლის მამრავლი",
+ "mod_epicloot_config_maxunitsperentry": "მაქს. ერთეული ჩანაწერზე",
+ "mod_epicloot_config_maxaddedunitsperdeath": "მაქს. დამატებული ერთეული მკვლელობაზე",
+ "mod_epicloot_config_rollbase": "გათამაშების ბაზა",
+ "mod_epicloot_config_rollpervalue": "გათამაშება მნიშვნელობაზე",
+ "mod_epicloot_config_rollcurve": "გათამაშების მრუდი",
+ "mod_epicloot_config_floorfraction": "ქვედა ზღვრის წილი",
+ "mod_epicloot_config_chitin": "ქიტინი",
+ "mod_epicloot_config_copperscrap": "სპილენძის ჯართი",
+ "mod_epicloot_config_ironscrap": "რკინის ჯართი",
+ "mod_epicloot_config_runestoneepic": "ეპიკური რანსტოუნი",
+ "mod_epicloot_config_runestonelegendary": "ლეგენდარული რანსტოუნი",
"mod_epicloot_me_modifylowhealth_config_max": "დაბალი ჯანმრთელობის ზღვრის ჭერი",
"mod_epicloot_me_riches_config_coins": "მონეტების ღირებულება",
"mod_epicloot_me_riches_config_amber": "ქარვის ღირებულება",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(სამუდამო)",
"mod_epicloot_legendary_brokkrgift": "ლეგენდარული ბროკის ძღვენი",
"mod_epicloot_mythic_brokkrgift": "მითიური ბროკის ძღვენი",
+ "mod_epicloot_ancient_brokkrgift": "უძველესი ბროკის ძღვენი",
"mod_epicloot_slotchisel_introduce": "უკიდურესად ძლიერი არტეფაქტი, რომელიც, გადმოცემით, ნებისმიერ ჯადოსნურ ნივთში ნატეხის ბუდეს ატანს.",
"mod_epicloot_slotchisel_hint": "გადაათრიეთ ჯადოსნურ ნივთზე ნატეხის ბუდის დასამატებლად",
"mod_epicloot_slotchisel_confirm_title": "დაემატოს ნატეხის ბუდე?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "ყოველი გადაგორება აშორებს {0} წამს შხამისა და წვის ეფექტებს.",
"mod_epicloot_me_stormrider_display": "+{0}% მოძრაობის სიჩქარე ქარიშხალში",
"mod_epicloot_me_stormrider_desc": "+{0}% მოძრაობის სიჩქარე, სანამ ქარიშხალი მძვინვარებს.",
- "mod_epicloot_me_shockingcharge_display": "მაშოკებელი მუხტი",
- "mod_epicloot_me_shockingcharge_desc": "ბრძოლაში მიყენებული დარტყმები აგროვებს მუხტს. სავსეობისას თქვენი შემდეგი დარტყმა წინ ისვრის ელვის დარტყმით ტალღას, რომელიც წინა დარტყმის ზიანის ნაწილს აყენებს.",
- "mod_epicloot_me_forestsaid_display": "ტყის დახმარება",
- "mod_epicloot_me_forestsaid_desc": "დარტყმის მიღებისას მჭიდრო ფესვები ამოიჭრება მიწიდან და ახლომდებარე მტრებს იჭერს.",
- "mod_epicloot_me_corpserot_display": "გვამის ლპობა",
- "mod_epicloot_me_corpserot_desc": "მოკლული მტრები შეიძლება შხამის ღრუბლად აფეთქდნენ და ახლომდებარე მტრები მოწამლონ.",
- "mod_epicloot_me_icyretribution_display": "ყინულოვანი შურისძიება",
- "mod_epicloot_me_icyretribution_desc": "დარტყმის მიღებისას თქვენს ირგვლივ ყინვის აფეთქებას იწვევს. განმუხტვის დრო იშვიათობის მიხედვით იცვლება.",
+ "mod_epicloot_me_shockingcharge_display": "მაშოკებელი მუხტი: ყოველ {0:0.#} დარტყმაზე",
+ "mod_epicloot_me_shockingcharge_desc": "ბრძოლაში ყოველ {0:0.#} დარტყმაზე შემდეგი დარტყმა წინ ისვრის ელვის დარტყმით ტალღას, რომელიც ამ დარტყმების ზიანის {1:0.#}%-ს აყენებს. უფრო მაღალი იშვიათობა ნაკლებ დარტყმას მოითხოვს.",
+ "mod_epicloot_me_forestsaid_display": "ტყის დახმარება: {0:0.#} მ, {1:0.#} წმ დაყოვნება",
+ "mod_epicloot_me_forestsaid_desc": "დარტყმის მიღებისას მჭიდრო ფესვები ამოიჭრება მიწიდან და {0:0.#} მეტრის რადიუსში მტრებს იჭერს. უფრო მაღალი იშვიათობა შორს აღწევს და {1:0.#} წამში ივსება.",
+ "mod_epicloot_me_corpserot_display": "გვამის ლპობა: {0:0.#} შხამი",
+ "mod_epicloot_me_corpserot_desc": "მოკლული მტრები შეიძლება შხამის ღრუბლად აფეთქდნენ და ახლომდებარე მტრებს {0:0.#} შხამის ზიანი მიაყენონ.",
+ "mod_epicloot_me_icyretribution_display": "ყინულოვანი შურისძიება: {0:0.#} ყინვა",
+ "mod_epicloot_me_icyretribution_desc": "დარტყმის მიღებისას თქვენს ირგვლივ ყინვის აფეთქებას იწვევს, რომელიც {0:0.#} ყინვის ზიანს აყენებს. უფრო მაღალი იშვიათობა უფრო ძლიერად ურტყამს და უფრო სწრაფად ივსება.",
"mod_epicloot_me_meteorsummoner_display": "მეტეორის მომხმობი: დარტყმის ზიანის {0}-ჯერ",
"mod_epicloot_me_meteorsummoner_desc": "ამ იარაღით ყოველი {1} დარტყმის შემდეგ მომდევნო დარტყმა სამიზნეზე მოგიზგიზე მეტეორს ჩამოაგდებს, რომელიც იმ დარტყმის {0}-ჯერად ცეცხლის ზიანს აყენებს.",
- "mod_epicloot_me_everflow_display": "დედოფლის მარადდინება",
- "mod_epicloot_me_everflow_desc": "არსებების მოკვლა გაძლევთ დამგროვებელ გაძლიერებას, რომელიც ზრდის ჯანმრთელობის, სტამინისა და ეიტრის აღდგენას.",
- "mod_epicloot_me_necroticfire_display": "მკვდარი ცეცხლი",
- "mod_epicloot_me_necroticfire_desc": "აშორებს იარაღს მთელ ფიზიკურ ზიანს და მთელ ამ ოდენობას ამატებს როგორც ცეცხლის, ისე შხამის ზიანში.",
+ "mod_epicloot_me_everflow_display": "დედოფლის მარადდინება: +{0:0.#}% აღდგენა თითო დონეზე",
+ "mod_epicloot_me_everflow_desc": "არსებების მოკვლა გაძლევთ დამგროვებელ გაძლიერებას, რომელიც ჯანმრთელობის, სტამინისა და ეიტრის აღდგენას {0:0.#}%-ით ზრდის თითო დონეზე, მაქსიმუმ {1:0.#} დონემდე.",
+ "mod_epicloot_me_necroticfire_display": "მკვდარი ცეცხლი: +{0:0.#}% ცეცხლი და შხამი",
+ "mod_epicloot_me_necroticfire_desc": "აშორებს იარაღს მთელ ფიზიკურ ზიანს და მისგან {0:0.#}%-ს ამატებს ცეცხლის ზიანად, იმავე ოდენობას კი შხამის ზიანად.",
"mod_epicloot_circlet": "გამკლება",
"mod_epicloot_belt": "ქსელი",
"mod_epicloot_remains": "დარჩილები",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "ლეგენდარული {0}",
"mod_epicloot_basicmythicnameformat": "მითიკური {0}",
+ "mod_epicloot_basicancientnameformat": "უძველესი {0}",
"mod_epicloot_rare_prefix1": "უცხო",
"mod_epicloot_rare_prefix2": "რეალური",
"mod_epicloot_rare_prefix3": "კრიპტული",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "ეპიკური",
"mod_epicloot_Legendary": "ლეგენდარული",
"mod_epicloot_Mythic": "მითიური",
+ "mod_epicloot_Ancient": "უძველესი",
"mod_epicloot_search_placeholder": "ძიება...",
"mod_epicloot_no_active_adventures": "მოქმედი თავგადასავალი არ არის",
"mod_epicloot_set_bonuses": "ჯგუფური ბონუსები",
diff --git a/EpicLoot/localizations/German.json b/EpicLoot/localizations/German.json
index a8048295b..3d8ce7560 100644
--- a/EpicLoot/localizations/German.json
+++ b/EpicLoot/localizations/German.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Feuerstein",
"mod_epicloot_config_serpentscale": "Seeschlangenschuppe",
"mod_epicloot_config_triplechance": "Chance auf Dreifachfang",
- "mod_epicloot_config_secondspercent": "Sekunden pro 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekunden pro 1 %",
"mod_epicloot_config_procchance": "Auslösechance",
"mod_epicloot_config_bonusrollsmin": "Min. Bonus-Beutewürfe",
"mod_epicloot_config_bonusrollsmax": "Max. Bonus-Beutewürfe",
+ "mod_epicloot_config_cooldown": "Abklingzeit (s)",
+ "mod_epicloot_config_basecooldown": "Basis-Abklingzeit (s)",
+ "mod_epicloot_config_cooldownperrarity": "Abklingzeit pro Seltenheit (s)",
+ "mod_epicloot_config_cooldownpervalue": "Abklingzeit pro Wert (s)",
+ "mod_epicloot_config_buffduration": "Dauer (s)",
+ "mod_epicloot_config_tickinterval": "Tick-Intervall (s)",
+ "mod_epicloot_config_radius": "Radius (m)",
+ "mod_epicloot_config_baseradius": "Basisradius (m)",
+ "mod_epicloot_config_radiuspertier": "Radius pro Wert (m)",
+ "mod_epicloot_config_maxcharges": "Treffer bis Aufladung",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Treffer bis Aufladung (Basis)",
+ "mod_epicloot_config_chargespervalue": "Gesparte Treffer pro Wert",
+ "mod_epicloot_config_mincharges": "Min. Treffer bis Aufladung",
+ "mod_epicloot_config_mincooldown": "Min. Abklingzeit (s)",
+ "mod_epicloot_config_damagepertrigger": "Schaden pro Auslösung",
+ "mod_epicloot_config_eitrpertrigger": "Eitr pro Auslösung",
+ "mod_epicloot_config_damagemultiplier": "Schadensmultiplikator",
+ "mod_epicloot_config_damagefraction": "Schadensanteil",
+ "mod_epicloot_config_damagepervalue": "Schaden pro Wert",
+ "mod_epicloot_config_frostpertier": "Frostschaden pro Wert",
+ "mod_epicloot_config_poisonpertier": "Giftschaden pro Wert",
+ "mod_epicloot_config_spawnheight": "Erscheinungshöhe (m)",
+ "mod_epicloot_config_spawnradius": "Erscheinungsradius (m)",
+ "mod_epicloot_config_spawninterval": "Erscheinungsintervall (s)",
+ "mod_epicloot_config_mindistance": "Min. Abschussdistanz (m)",
+ "mod_epicloot_config_maxdistance": "Max. Abschussdistanz (m)",
+ "mod_epicloot_config_projectilespeed": "Projektilgeschwindigkeit (m/s)",
+ "mod_epicloot_config_explosionradius": "Explosionsradius (m)",
+ "mod_epicloot_config_conelength": "Kegellänge (m)",
+ "mod_epicloot_config_conemaxwidth": "Kegelbreite (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Ignorierfenster nach Entladung (s)",
+ "mod_epicloot_config_minmovespeed": "Min. Bewegungstempo",
+ "mod_epicloot_config_patchradius": "Flächenradius (m)",
+ "mod_epicloot_config_patchlifetime": "Flächendauer (s)",
+ "mod_epicloot_config_patchtickinterval": "Tick-Intervall der Fläche (s)",
+ "mod_epicloot_config_speedfloor": "Mindesttempo",
+ "mod_epicloot_config_batlifetime": "Lebensdauer der Fledermaus (s)",
+ "mod_epicloot_config_summoncountoffset": "Versatz der Beschwörungsanzahl",
+ "mod_epicloot_config_concurrentpersummon": "Max. gleichzeitig pro Beschwörung",
+ "mod_epicloot_config_maxreduction": "Max. Reduzierung",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% der max. Gesundheit pro Wert",
+ "mod_epicloot_config_minhealthreduction": "Min. Gesundheitsreduzierung",
+ "mod_epicloot_config_carryweightpervalue": "Tragegewicht pro Wert",
+ "mod_epicloot_config_minresultingcarryweight": "Min. Tragegewicht",
+ "mod_epicloot_config_weightthreshold": "Gewichtsschwelle",
+ "mod_epicloot_config_weightperbonus": "Gewicht pro Bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-Kosten pro Schaden",
+ "mod_epicloot_config_minskilllevel": "Min. Fertigkeitsstufe",
+ "mod_epicloot_config_levelweightexponent": "Exponent der Levelgewichtung",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. Stufenaufstiege pro Auslösung",
+ "mod_epicloot_config_maxmultiplier": "Max. Beutemultiplikator",
+ "mod_epicloot_config_maxunitsperentry": "Max. Einheiten pro Eintrag",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. zusätzliche Einheiten pro Kill",
+ "mod_epicloot_config_rollbase": "Wurfbasis",
+ "mod_epicloot_config_rollpervalue": "Wurf pro Wert",
+ "mod_epicloot_config_rollcurve": "Wurfkurve",
+ "mod_epicloot_config_floorfraction": "Untergrenzenanteil",
+ "mod_epicloot_config_chitin": "Chitin",
+ "mod_epicloot_config_copperscrap": "Kupferschrott",
+ "mod_epicloot_config_ironscrap": "Eisenschrott",
+ "mod_epicloot_config_runestoneepic": "Epischer Runenstein",
+ "mod_epicloot_config_runestonelegendary": "Legendärer Runenstein",
"mod_epicloot_me_modifylowhealth_config_max": "Maximale Schwelle für niedrige Gesundheit",
"mod_epicloot_me_riches_config_coins": "Münzwert",
"mod_epicloot_me_riches_config_amber": "Bernsteinwert",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(dauerhaft)",
"mod_epicloot_legendary_brokkrgift": "Legendäre Gabe Brokkrs",
"mod_epicloot_mythic_brokkrgift": "Mythische Gabe Brokkrs",
+ "mod_epicloot_ancient_brokkrgift": "Uralte Gabe Brokkrs",
"mod_epicloot_slotchisel_introduce": "Ein überaus mächtiges Artefakt, das angeblich jedem magischen Gegenstand einen Splitter-Steckplatz einfügt.",
"mod_epicloot_slotchisel_hint": "Auf einen magischen Gegenstand ziehen, um einen Splitter-Steckplatz hinzuzufügen",
"mod_epicloot_slotchisel_confirm_title": "Splitter-Steckplatz hinzufügen?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Jede Ausweichrolle entfernt {0} Sekunden von deinen Gift- und Brandeffekten.",
"mod_epicloot_me_stormrider_display": "+{0} % Bewegungstempo im Sturm",
"mod_epicloot_me_stormrider_desc": "+{0} % Bewegungstempo, während ein Sturm tobt.",
- "mod_epicloot_me_shockingcharge_display": "Schockende Ladung",
- "mod_epicloot_me_shockingcharge_desc": "Deine Kampftreffer bauen Ladungen auf. Bei voller Ladung entfesselt der nächste Treffer eine nach vorn gerichtete Blitzschockwelle, die einen Teil des Schadens deiner vorherigen Treffer verursacht.",
- "mod_epicloot_me_forestsaid_display": "Hilfe des Waldes",
- "mod_epicloot_me_forestsaid_desc": "Wirst du getroffen, brechen fesselnde Wurzeln hervor und halten Gegner in der Nähe fest.",
- "mod_epicloot_me_corpserot_display": "Leichenfäule",
- "mod_epicloot_me_corpserot_desc": "Von dir getötete Gegner können in einer Giftwolke zerplatzen und Feinde in der Nähe vergiften.",
- "mod_epicloot_me_icyretribution_display": "Eisige Vergeltung",
- "mod_epicloot_me_icyretribution_desc": "Wirst du getroffen, zündest du eine Frostnova um dich herum. Die Abklingzeit skaliert mit der Seltenheit.",
+ "mod_epicloot_me_shockingcharge_display": "Schockende Ladung: alle {0:0.#} Treffer",
+ "mod_epicloot_me_shockingcharge_desc": "Alle {0:0.#} Kampftreffer entfesselt der nächste Treffer eine nach vorn gerichtete Blitzschockwelle, die {1:0.#} % des Schadens dieser Treffer verursacht. Eine höhere Seltenheit benötigt weniger Treffer.",
+ "mod_epicloot_me_forestsaid_display": "Hilfe des Waldes: {0:0.#} m, {1:0.#} s Abklingzeit",
+ "mod_epicloot_me_forestsaid_desc": "Wirst du getroffen, brechen fesselnde Wurzeln hervor und halten Gegner im Umkreis von {0:0.#} Metern fest. Eine höhere Seltenheit reicht weiter und lädt in {1:0.#} Sekunden nach.",
+ "mod_epicloot_me_corpserot_display": "Leichenfäule: {0:0.#} Gift",
+ "mod_epicloot_me_corpserot_desc": "Von dir getötete Gegner können in einer Giftwolke zerplatzen und Feinde in der Nähe mit {0:0.#} Giftschaden treffen.",
+ "mod_epicloot_me_icyretribution_display": "Eisige Vergeltung: {0:0.#} Frost",
+ "mod_epicloot_me_icyretribution_desc": "Wirst du getroffen, zündest du eine Frostnova um dich herum, die {0:0.#} Frostschaden verursacht. Eine höhere Seltenheit trifft härter und lädt schneller nach.",
"mod_epicloot_me_meteorsummoner_display": "Meteorbeschwörer: {0}-facher Trefferschaden",
"mod_epicloot_me_meteorsummoner_desc": "Alle {1} Waffentreffer ruft dein nächster Treffer einen feurigen Meteor auf das Ziel herab, der das {0}-fache des Schadens dieses Treffers als Feuerschaden verursacht.",
- "mod_epicloot_me_everflow_display": "Ewiger Fluss der Königin",
- "mod_epicloot_me_everflow_desc": "Das Töten einer Kreatur gewährt einen stapelbaren Effekt, der deine Gesundheits-, Ausdauer- und Eitr-Regeneration steigert.",
- "mod_epicloot_me_necroticfire_display": "Nekrotisches Feuer",
- "mod_epicloot_me_necroticfire_desc": "Entzieht deiner Waffe allen physischen Schaden und fügt diesen vollen Betrag sowohl als Feuer- als auch als Giftschaden hinzu.",
+ "mod_epicloot_me_everflow_display": "Ewiger Fluss der Königin: +{0:0.#} % Regeneration pro Stapel",
+ "mod_epicloot_me_everflow_desc": "Das Töten einer Kreatur gewährt einen stapelbaren Effekt, der deine Gesundheits-, Ausdauer- und Eitr-Regeneration um {0:0.#} % pro Stapel steigert, bis zu {1:0.#} Stapel.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotisches Feuer: +{0:0.#} % Feuer und Gift",
+ "mod_epicloot_me_necroticfire_desc": "Entzieht deiner Waffe allen physischen Schaden und fügt {0:0.#} % davon als Feuerschaden hinzu sowie ebenso viel als Giftschaden.",
"mod_epicloot_circlet": "Kreis",
"mod_epicloot_belt": "Gürtel",
"mod_epicloot_remains": "Überreste",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendär {0}",
"mod_epicloot_basicmythicnameformat": "Mythisch {0}",
+ "mod_epicloot_basicancientnameformat": "Uralt {0}",
"mod_epicloot_rare_prefix1": "Seltsam",
"mod_epicloot_rare_prefix2": "Selten",
"mod_epicloot_rare_prefix3": "Kryptisch",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epos",
"mod_epicloot_Legendary": "Legendär",
"mod_epicloot_Mythic": "Mythisch",
+ "mod_epicloot_Ancient": "Uralt",
"mod_epicloot_search_placeholder": "Suche...",
"mod_epicloot_no_active_adventures": "Keine aktiven Abenteuer",
"mod_epicloot_set_bonuses": "Set-Boni",
diff --git a/EpicLoot/localizations/Greek.json b/EpicLoot/localizations/Greek.json
index bd86c25de..b29e18306 100644
--- a/EpicLoot/localizations/Greek.json
+++ b/EpicLoot/localizations/Greek.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Πυριτόλιθος",
"mod_epicloot_config_serpentscale": "Λέπι θαλάσσιου φιδιού",
"mod_epicloot_config_triplechance": "Πιθανότητα τριπλής ψαριάς",
- "mod_epicloot_config_secondspercent": "Δευτερόλεπτα ανά 1 %",
+ "mod_epicloot_config_secondsperpercent": "Δευτερόλεπτα ανά 1 %",
"mod_epicloot_config_procchance": "Πιθανότητα ενεργοποίησης",
"mod_epicloot_config_bonusrollsmin": "Ελάχ. μπόνους ρίψεις λείας",
"mod_epicloot_config_bonusrollsmax": "Μέγ. μπόνους ρίψεις λείας",
+ "mod_epicloot_config_cooldown": "Αναμονή (δευτ.)",
+ "mod_epicloot_config_basecooldown": "Βασική αναμονή (δευτ.)",
+ "mod_epicloot_config_cooldownperrarity": "Αναμονή ανά σπανιότητα (δευτ.)",
+ "mod_epicloot_config_cooldownpervalue": "Αναμονή ανά τιμή (δευτ.)",
+ "mod_epicloot_config_buffduration": "Διάρκεια (δευτ.)",
+ "mod_epicloot_config_tickinterval": "Διάστημα παλμού (δευτ.)",
+ "mod_epicloot_config_radius": "Ακτίνα (μ)",
+ "mod_epicloot_config_baseradius": "Βασική ακτίνα (μ)",
+ "mod_epicloot_config_radiuspertier": "Ακτίνα ανά τιμή (μ)",
+ "mod_epicloot_config_maxcharges": "Χτυπήματα για φόρτιση",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Χτυπήματα για φόρτιση (βασικό)",
+ "mod_epicloot_config_chargespervalue": "Χτυπήματα που εξοικονομούνται ανά τιμή",
+ "mod_epicloot_config_mincharges": "Ελάχ. χτυπήματα για φόρτιση",
+ "mod_epicloot_config_mincooldown": "Ελάχ. αναμονή (δευτ.)",
+ "mod_epicloot_config_damagepertrigger": "Ζημιά ανά ενεργοποίηση",
+ "mod_epicloot_config_eitrpertrigger": "Έιτρ ανά ενεργοποίηση",
+ "mod_epicloot_config_damagemultiplier": "Πολλαπλασιαστής ζημιάς",
+ "mod_epicloot_config_damagefraction": "Κλάσμα ζημιάς",
+ "mod_epicloot_config_damagepervalue": "Ζημιά ανά τιμή",
+ "mod_epicloot_config_frostpertier": "Ζημιά παγετού ανά τιμή",
+ "mod_epicloot_config_poisonpertier": "Ζημιά δηλητηρίου ανά τιμή",
+ "mod_epicloot_config_spawnheight": "Ύψος εμφάνισης (μ)",
+ "mod_epicloot_config_spawnradius": "Ακτίνα εμφάνισης (μ)",
+ "mod_epicloot_config_spawninterval": "Διάστημα εμφάνισης (δευτ.)",
+ "mod_epicloot_config_mindistance": "Ελάχ. απόσταση εκτόξευσης (μ)",
+ "mod_epicloot_config_maxdistance": "Μέγ. απόσταση εκτόξευσης (μ)",
+ "mod_epicloot_config_projectilespeed": "Ταχύτητα βλήματος (μ/δευτ.)",
+ "mod_epicloot_config_explosionradius": "Ακτίνα έκρηξης (μ)",
+ "mod_epicloot_config_conelength": "Μήκος κώνου (μ)",
+ "mod_epicloot_config_conemaxwidth": "Πλάτος κώνου (μ)",
+ "mod_epicloot_config_dischargeignorewindow": "Παράθυρο αγνόησης εκφόρτισης (δευτ.)",
+ "mod_epicloot_config_minmovespeed": "Ελάχ. ταχύτητα κίνησης",
+ "mod_epicloot_config_patchradius": "Ακτίνα ζώνης (μ)",
+ "mod_epicloot_config_patchlifetime": "Διάρκεια ζώνης (δευτ.)",
+ "mod_epicloot_config_patchtickinterval": "Διάστημα παλμού ζώνης (δευτ.)",
+ "mod_epicloot_config_speedfloor": "Κατώτατη ταχύτητα",
+ "mod_epicloot_config_batlifetime": "Διάρκεια νυχτερίδας (δευτ.)",
+ "mod_epicloot_config_summoncountoffset": "Μετατόπιση αριθμού κλήσεων",
+ "mod_epicloot_config_concurrentpersummon": "Μέγ. ταυτόχρονα ανά κλήση",
+ "mod_epicloot_config_maxreduction": "Μέγ. μείωση",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% μέγ. υγείας ανά τιμή",
+ "mod_epicloot_config_minhealthreduction": "Ελάχ. μείωση υγείας",
+ "mod_epicloot_config_carryweightpervalue": "Ικανότητα μεταφοράς ανά τιμή",
+ "mod_epicloot_config_minresultingcarryweight": "Ελάχ. ικανότητα μεταφοράς",
+ "mod_epicloot_config_weightthreshold": "Κατώφλι βάρους",
+ "mod_epicloot_config_weightperbonus": "Βάρος ανά μπόνους",
+ "mod_epicloot_config_eitrcostperdamage": "Κόστος έιτρ ανά ζημιά",
+ "mod_epicloot_config_minskilllevel": "Ελάχ. επίπεδο δεξιότητας",
+ "mod_epicloot_config_levelweightexponent": "Εκθέτης βαρύτητας επιπέδου",
+ "mod_epicloot_config_maxlevelupsperproc": "Μέγ. άνοδοι επιπέδου ανά ενεργοποίηση",
+ "mod_epicloot_config_maxmultiplier": "Μέγ. πολλαπλασιαστής λείας",
+ "mod_epicloot_config_maxunitsperentry": "Μέγ. μονάδες ανά καταχώρηση",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Μέγ. πρόσθετες μονάδες ανά θάνατο",
+ "mod_epicloot_config_rollbase": "Βάση ρίψης",
+ "mod_epicloot_config_rollpervalue": "Ρίψη ανά τιμή",
+ "mod_epicloot_config_rollcurve": "Καμπύλη ρίψης",
+ "mod_epicloot_config_floorfraction": "Κλάσμα κατωφλίου",
+ "mod_epicloot_config_chitin": "Χιτίνη",
+ "mod_epicloot_config_copperscrap": "Θραύσματα χαλκού",
+ "mod_epicloot_config_ironscrap": "Θραύσματα σιδήρου",
+ "mod_epicloot_config_runestoneepic": "Επική ρουνική πέτρα",
+ "mod_epicloot_config_runestonelegendary": "Θρυλική ρουνική πέτρα",
"mod_epicloot_me_modifylowhealth_config_max": "Μέγιστο κατώφλι χαμηλής υγείας",
"mod_epicloot_me_riches_config_coins": "Αξία νομισμάτων",
"mod_epicloot_me_riches_config_amber": "Αξία κεχριμπαριού",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(μόνιμο)",
"mod_epicloot_legendary_brokkrgift": "Θρυλικό Δώρο του Μπρόκρ",
"mod_epicloot_mythic_brokkrgift": "Μυθικό Δώρο του Μπρόκρ",
+ "mod_epicloot_ancient_brokkrgift": "Αρχαίο Δώρο του Μπρόκρ",
"mod_epicloot_slotchisel_introduce": "Ένα εξαιρετικά ισχυρό τεχνούργημα που, όπως λέγεται, ενσωματώνει μια υποδοχή θραύσματος σε οποιοδήποτε μαγικό αντικείμενο.",
"mod_epicloot_slotchisel_hint": "Σύρε το πάνω σε ένα μαγικό αντικείμενο για να προσθέσεις υποδοχή θραύσματος",
"mod_epicloot_slotchisel_confirm_title": "Προσθήκη υποδοχής θραύσματος;",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Κάθε κύλιση αποφυγής αφαιρεί {0} δευτερόλεπτα από τα εφέ δηλητηρίου και καψίματος.",
"mod_epicloot_me_stormrider_display": "+{0} % ταχύτητα κίνησης στις καταιγίδες",
"mod_epicloot_me_stormrider_desc": "+{0} % ταχύτητα κίνησης όσο μαίνεται καταιγίδα.",
- "mod_epicloot_me_shockingcharge_display": "Ηλεκτρισμένη φόρτιση",
- "mod_epicloot_me_shockingcharge_desc": "Τα χτυπήματά σου στη μάχη συσσωρεύουν φορτίσεις. Στη μέγιστη φόρτιση, το επόμενο χτύπημα εξαπολύει ένα ωστικό κύμα κεραυνού προς τα εμπρός που προκαλεί μέρος της ζημιάς των προηγούμενων χτυπημάτων σου.",
- "mod_epicloot_me_forestsaid_display": "Βοήθεια του δάσους",
- "mod_epicloot_me_forestsaid_desc": "Όταν χτυπηθείς, ξεπροβάλλουν ρίζες που παγιδεύουν και ακινητοποιούν τους κοντινούς εχθρούς.",
- "mod_epicloot_me_corpserot_display": "Πτωματική σήψη",
- "mod_epicloot_me_corpserot_desc": "Οι εχθροί που σκοτώνεις μπορούν να σκάσουν σε ένα σύννεφο δηλητηρίου, δηλητηριάζοντας τους κοντινούς αντιπάλους.",
- "mod_epicloot_me_icyretribution_display": "Παγερή ανταπόδοση",
- "mod_epicloot_me_icyretribution_desc": "Όταν χτυπηθείς, πυροδοτείς μια παγερή νόβα γύρω σου. Ο χρόνος επαναφόρτισης κλιμακώνεται με τη σπανιότητα.",
+ "mod_epicloot_me_shockingcharge_display": "Ηλεκτρισμένη φόρτιση: κάθε {0:0.#} χτυπήματα",
+ "mod_epicloot_me_shockingcharge_desc": "Κάθε {0:0.#} χτυπήματα στη μάχη, το επόμενο χτύπημα εξαπολύει ένα ωστικό κύμα κεραυνού προς τα εμπρός που προκαλεί {1:0.#}% της ζημιάς αυτών των χτυπημάτων. Μεγαλύτερη σπανιότητα απαιτεί λιγότερα χτυπήματα.",
+ "mod_epicloot_me_forestsaid_display": "Βοήθεια του δάσους: {0:0.#} μ, {1:0.#} δευτ. αναμονή",
+ "mod_epicloot_me_forestsaid_desc": "Όταν χτυπηθείς, ξεπροβάλλουν ρίζες που ακινητοποιούν τους εχθρούς σε ακτίνα {0:0.#} μέτρων. Μεγαλύτερη σπανιότητα φτάνει πιο μακριά και επαναφορτίζεται σε {1:0.#} δευτερόλεπτα.",
+ "mod_epicloot_me_corpserot_display": "Πτωματική σήψη: {0:0.#} δηλητήριο",
+ "mod_epicloot_me_corpserot_desc": "Οι εχθροί που σκοτώνεις μπορούν να σκάσουν σε ένα σύννεφο δηλητηρίου, προκαλώντας {0:0.#} ζημιά δηλητηρίου στους κοντινούς αντιπάλους.",
+ "mod_epicloot_me_icyretribution_display": "Παγερή ανταπόδοση: {0:0.#} πάγος",
+ "mod_epicloot_me_icyretribution_desc": "Όταν χτυπηθείς, πυροδοτείς μια παγερή νόβα γύρω σου που προκαλεί {0:0.#} ζημιά πάγου. Μεγαλύτερη σπανιότητα χτυπά δυνατότερα και επαναφορτίζεται γρηγορότερα.",
"mod_epicloot_me_meteorsummoner_display": "Επικαλεστής μετεωριτών: {0}× ζημιά χτυπήματος",
"mod_epicloot_me_meteorsummoner_desc": "Κάθε {1} χτυπήματα όπλου, το επόμενο χτύπημά σου καλεί έναν φλεγόμενο μετεωρίτη στον στόχο που προκαλεί {0}× τη ζημιά εκείνου του χτυπήματος ως φωτιά.",
- "mod_epicloot_me_everflow_display": "Αέναη ροή της Βασίλισσας",
- "mod_epicloot_me_everflow_desc": "Η θανάτωση ενός πλάσματος δίνει μια στοιβαζόμενη ενίσχυση που αυξάνει την αναγέννηση υγείας, αντοχής και έιτρ.",
- "mod_epicloot_me_necroticfire_display": "Νεκρωτική φωτιά",
- "mod_epicloot_me_necroticfire_desc": "Αφαιρεί όλη τη σωματική ζημιά από το όπλο σου και προσθέτει αυτό το πλήρες ποσό τόσο ως ζημιά φωτιάς όσο και ως ζημιά δηλητηρίου.",
+ "mod_epicloot_me_everflow_display": "Αέναη ροή της Βασίλισσας: +{0:0.#}% αναγέννηση ανά στοίβα",
+ "mod_epicloot_me_everflow_desc": "Η θανάτωση ενός πλάσματος δίνει μια στοιβαζόμενη ενίσχυση που αυξάνει την αναγέννηση υγείας, αντοχής και έιτρ κατά {0:0.#}% ανά στοίβα, έως {1:0.#} στοίβες.",
+ "mod_epicloot_me_necroticfire_display": "Νεκρωτική φωτιά: +{0:0.#}% φωτιά και δηλητήριο",
+ "mod_epicloot_me_necroticfire_desc": "Αφαιρεί όλη τη σωματική ζημιά από το όπλο σου και προσθέτει {0:0.#}% από αυτή ως ζημιά φωτιάς και άλλο τόσο ως ζημιά δηλητηρίου.",
"mod_epicloot_circlet": "στέμμα",
"mod_epicloot_belt": "ζώνη",
"mod_epicloot_remains": "παραμείνει",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Θρυλικό {0}",
"mod_epicloot_basicmythicnameformat": "Μυθικό {0}",
+ "mod_epicloot_basicancientnameformat": "Αρχαίο {0}",
"mod_epicloot_rare_prefix1": "Παράξενο",
"mod_epicloot_rare_prefix2": "Σπάνιο",
"mod_epicloot_rare_prefix3": "Κρυπτικό",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Επικό",
"mod_epicloot_Legendary": "Θρυλικό",
"mod_epicloot_Mythic": "Μυθικό",
+ "mod_epicloot_Ancient": "Αρχαίο",
"mod_epicloot_search_placeholder": "Αναζήτηση...",
"mod_epicloot_no_active_adventures": "Δεν υπάρχουν ενεργές περιπέτειες",
"mod_epicloot_set_bonuses": "Μπόνους σετ",
diff --git a/EpicLoot/localizations/Hindi.json b/EpicLoot/localizations/Hindi.json
index 9efbb1caa..ecefea649 100644
--- a/EpicLoot/localizations/Hindi.json
+++ b/EpicLoot/localizations/Hindi.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "चकमक पत्थर",
"mod_epicloot_config_serpentscale": "सर्प शल्क",
"mod_epicloot_config_triplechance": "तिहरी पकड़ की संभावना",
- "mod_epicloot_config_secondspercent": "प्रति 1% सेकंड",
+ "mod_epicloot_config_secondsperpercent": "प्रति 1% सेकंड",
"mod_epicloot_config_procchance": "सक्रियण संभावना",
"mod_epicloot_config_bonusrollsmin": "न्यूनतम अतिरिक्त लूट घुमाव",
"mod_epicloot_config_bonusrollsmax": "अधिकतम अतिरिक्त लूट घुमाव",
+ "mod_epicloot_config_cooldown": "कूलडाउन (से)",
+ "mod_epicloot_config_basecooldown": "आधार कूलडाउन (से)",
+ "mod_epicloot_config_cooldownperrarity": "प्रति रेयरिटी कूलडाउन (से)",
+ "mod_epicloot_config_cooldownpervalue": "प्रति मान कूलडाउन (से)",
+ "mod_epicloot_config_buffduration": "अवधि (से)",
+ "mod_epicloot_config_tickinterval": "टिक अंतराल (से)",
+ "mod_epicloot_config_radius": "त्रिज्या (मी)",
+ "mod_epicloot_config_baseradius": "आधार त्रिज्या (मी)",
+ "mod_epicloot_config_radiuspertier": "प्रति मान त्रिज्या (मी)",
+ "mod_epicloot_config_maxcharges": "चार्ज हेतु प्रहार",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "चार्ज हेतु प्रहार (आधार)",
+ "mod_epicloot_config_chargespervalue": "प्रति मान बचे प्रहार",
+ "mod_epicloot_config_mincharges": "न्यूनतम प्रहार चार्ज हेतु",
+ "mod_epicloot_config_mincooldown": "न्यूनतम कूलडाउन (से)",
+ "mod_epicloot_config_damagepertrigger": "प्रति सक्रियण क्षति",
+ "mod_epicloot_config_eitrpertrigger": "प्रति सक्रियण ईटर",
+ "mod_epicloot_config_damagemultiplier": "क्षति गुणक",
+ "mod_epicloot_config_damagefraction": "क्षति अंश",
+ "mod_epicloot_config_damagepervalue": "प्रति मान क्षति",
+ "mod_epicloot_config_frostpertier": "प्रति मान तुषार क्षति",
+ "mod_epicloot_config_poisonpertier": "प्रति मान विष क्षति",
+ "mod_epicloot_config_spawnheight": "उत्पत्ति ऊँचाई (मी)",
+ "mod_epicloot_config_spawnradius": "उत्पत्ति त्रिज्या (मी)",
+ "mod_epicloot_config_spawninterval": "उत्पत्ति अंतराल (से)",
+ "mod_epicloot_config_mindistance": "न्यूनतम प्रक्षेपण दूरी (मी)",
+ "mod_epicloot_config_maxdistance": "अधिकतम प्रक्षेपण दूरी (मी)",
+ "mod_epicloot_config_projectilespeed": "प्रक्षेप्य गति (मी/से)",
+ "mod_epicloot_config_explosionradius": "विस्फोट त्रिज्या (मी)",
+ "mod_epicloot_config_conelength": "शंकु लंबाई (मी)",
+ "mod_epicloot_config_conemaxwidth": "शंकु चौड़ाई (मी)",
+ "mod_epicloot_config_dischargeignorewindow": "निर्वहन अनदेखी अवधि (से)",
+ "mod_epicloot_config_minmovespeed": "न्यूनतम गति",
+ "mod_epicloot_config_patchradius": "क्षेत्र त्रिज्या (मी)",
+ "mod_epicloot_config_patchlifetime": "क्षेत्र अवधि (से)",
+ "mod_epicloot_config_patchtickinterval": "क्षेत्र टिक अंतराल (से)",
+ "mod_epicloot_config_speedfloor": "गति की निचली सीमा",
+ "mod_epicloot_config_batlifetime": "चमगादड़ अवधि (से)",
+ "mod_epicloot_config_summoncountoffset": "आह्वान संख्या समायोजन",
+ "mod_epicloot_config_concurrentpersummon": "प्रति आह्वान अधिकतम एक साथ",
+ "mod_epicloot_config_maxreduction": "अधिकतम कमी",
+ "mod_epicloot_config_maxhealthpercentpervalue": "प्रति मान अधिकतम स्वास्थ्य %",
+ "mod_epicloot_config_minhealthreduction": "न्यूनतम स्वास्थ्य कमी",
+ "mod_epicloot_config_carryweightpervalue": "प्रति मान वहन भार",
+ "mod_epicloot_config_minresultingcarryweight": "न्यूनतम वहन भार",
+ "mod_epicloot_config_weightthreshold": "भार सीमा",
+ "mod_epicloot_config_weightperbonus": "प्रति बोनस भार",
+ "mod_epicloot_config_eitrcostperdamage": "प्रति क्षति ईटर लागत",
+ "mod_epicloot_config_minskilllevel": "न्यूनतम कौशल स्तर",
+ "mod_epicloot_config_levelweightexponent": "स्तर भार घातांक",
+ "mod_epicloot_config_maxlevelupsperproc": "प्रति सक्रियण अधिकतम स्तर वृद्धि",
+ "mod_epicloot_config_maxmultiplier": "अधिकतम लूट गुणक",
+ "mod_epicloot_config_maxunitsperentry": "प्रति प्रविष्टि अधिकतम इकाइयाँ",
+ "mod_epicloot_config_maxaddedunitsperdeath": "प्रति वध अधिकतम जोड़ी इकाइयाँ",
+ "mod_epicloot_config_rollbase": "घुमाव आधार",
+ "mod_epicloot_config_rollpervalue": "प्रति मान घुमाव",
+ "mod_epicloot_config_rollcurve": "घुमाव वक्र",
+ "mod_epicloot_config_floorfraction": "निचली सीमा अंश",
+ "mod_epicloot_config_chitin": "काइटिन",
+ "mod_epicloot_config_copperscrap": "तांबे का स्क्रैप",
+ "mod_epicloot_config_ironscrap": "लोहे का स्क्रैप",
+ "mod_epicloot_config_runestoneepic": "महाकाव्य रनस्टोन",
+ "mod_epicloot_config_runestonelegendary": "प्रसिद्ध रनस्टोन",
"mod_epicloot_me_modifylowhealth_config_max": "कम स्वास्थ्य सीमा की उच्चतम हद",
"mod_epicloot_me_riches_config_coins": "सिक्कों का मूल्य",
"mod_epicloot_me_riches_config_amber": "एम्बर का मूल्य",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(स्थायी)",
"mod_epicloot_legendary_brokkrgift": "प्रसिद्ध ब्रोक्कर का उपहार",
"mod_epicloot_mythic_brokkrgift": "मिथिक ब्रोक्कर का उपहार",
+ "mod_epicloot_ancient_brokkrgift": "प्राचीन ब्रोक्कर का उपहार",
"mod_epicloot_slotchisel_introduce": "एक अत्यंत शक्तिशाली कलाकृति, जिसके बारे में कहा जाता है कि यह किसी भी जादुई वस्तु में एक खंड स्लॉट जड़ देती है।",
"mod_epicloot_slotchisel_hint": "खंड स्लॉट जोड़ने के लिए किसी जादुई वस्तु पर खींचें",
"mod_epicloot_slotchisel_confirm_title": "खंड स्लॉट जोड़ें?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "हर लोट-चकमा विष और जलन प्रभावों के {0} सेकंड हटा देता है।",
"mod_epicloot_me_stormrider_display": "तूफ़ान में +{0}% गति",
"mod_epicloot_me_stormrider_desc": "तूफ़ान चलते समय +{0}% गति।",
- "mod_epicloot_me_shockingcharge_display": "विद्युत आवेश",
- "mod_epicloot_me_shockingcharge_desc": "युद्ध में लगे प्रहार आवेश जमा करते हैं। पूरा होने पर आपका अगला प्रहार आगे की ओर एक विद्युत तरंग छोड़ता है जो पिछले प्रहार की क्षति का एक अंश करती है।",
- "mod_epicloot_me_forestsaid_display": "वन की सहायता",
- "mod_epicloot_me_forestsaid_desc": "चोट खाने पर उलझाने वाली जड़ें फूट पड़ती हैं और पास के शत्रुओं को जकड़ लेती हैं।",
- "mod_epicloot_me_corpserot_display": "शव सड़न",
- "mod_epicloot_me_corpserot_desc": "मारे गए शत्रु विष के बादल में फट सकते हैं जो पास के शत्रुओं को विषग्रस्त कर देता है।",
- "mod_epicloot_me_icyretribution_display": "हिमशीत प्रतिशोध",
- "mod_epicloot_me_icyretribution_desc": "चोट खाने पर आपके चारों ओर तुषार विस्फोट होता है। कूलडाउन दुर्लभता के अनुसार बदलता है।",
+ "mod_epicloot_me_shockingcharge_display": "विद्युत आवेश: हर {0:0.#} प्रहार पर",
+ "mod_epicloot_me_shockingcharge_desc": "युद्ध में हर {0:0.#} प्रहार के बाद अगला प्रहार आगे की ओर एक विद्युत तरंग छोड़ता है जो उन प्रहारों की क्षति का {1:0.#}% करती है। अधिक दुर्लभता में कम प्रहार लगते हैं।",
+ "mod_epicloot_me_forestsaid_display": "वन की सहायता: {0:0.#} मी, {1:0.#} से कूलडाउन",
+ "mod_epicloot_me_forestsaid_desc": "चोट खाने पर उलझाने वाली जड़ें फूट पड़ती हैं और {0:0.#} मीटर के भीतर शत्रुओं को जकड़ लेती हैं। अधिक दुर्लभता अधिक दूर तक पहुँचती है और {1:0.#} सेकंड में तैयार हो जाती है।",
+ "mod_epicloot_me_corpserot_display": "शव सड़न: {0:0.#} विष",
+ "mod_epicloot_me_corpserot_desc": "मारे गए शत्रु विष के बादल में फट सकते हैं जो पास के शत्रुओं को {0:0.#} विष क्षति देता है।",
+ "mod_epicloot_me_icyretribution_display": "हिमशीत प्रतिशोध: {0:0.#} तुषार",
+ "mod_epicloot_me_icyretribution_desc": "चोट खाने पर आपके चारों ओर तुषार विस्फोट होता है जो {0:0.#} तुषार क्षति करता है। अधिक दुर्लभता अधिक जोर से मारती है और जल्दी तैयार होती है।",
"mod_epicloot_me_meteorsummoner_display": "उल्का आह्वानकर्ता: प्रहार क्षति का {0} गुना",
"mod_epicloot_me_meteorsummoner_desc": "इस हथियार से हर {1} प्रहार के बाद अगला वार लक्ष्य पर एक जलती उल्का गिराता है जो उस वार की {0} गुना आग क्षति करती है।",
- "mod_epicloot_me_everflow_display": "रानी का सदाप्रवाह",
- "mod_epicloot_me_everflow_desc": "जीवों का वध एक परतदार बढ़ावा देता है जो स्वास्थ्य, स्टैमिना और ईटर पुनर्भरण बढ़ाता है।",
- "mod_epicloot_me_necroticfire_display": "गलित अग्नि",
- "mod_epicloot_me_necroticfire_desc": "हथियार से समस्त भौतिक क्षति हटा देता है और उतनी ही पूरी मात्रा आग क्षति तथा विष क्षति दोनों में जोड़ देता है।",
+ "mod_epicloot_me_everflow_display": "रानी का सदाप्रवाह: प्रति परत +{0:0.#}% पुनर्भरण",
+ "mod_epicloot_me_everflow_desc": "जीवों का वध एक परतदार बढ़ावा देता है जो प्रति परत स्वास्थ्य, स्टैमिना और ईटर पुनर्भरण {0:0.#}% बढ़ाता है, अधिकतम {1:0.#} परतें।",
+ "mod_epicloot_me_necroticfire_display": "गलित अग्नि: +{0:0.#}% आग और विष",
+ "mod_epicloot_me_necroticfire_desc": "हथियार से समस्त भौतिक क्षति हटा देता है और उसका {0:0.#}% आग क्षति के रूप में तथा उतना ही विष क्षति के रूप में जोड़ देता है।",
"mod_epicloot_circlet": "सर्कलेट",
"mod_epicloot_belt": "बेल्ट",
"mod_epicloot_remains": "शेष",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "पूर्वाग्रही {0}",
"mod_epicloot_basicmythicnameformat": "मिथिक {0}",
+ "mod_epicloot_basicancientnameformat": "प्राचीन {0}",
"mod_epicloot_rare_prefix1": "अजीब",
"mod_epicloot_rare_prefix2": "दुर्लभ",
"mod_epicloot_rare_prefix3": "रहस्यमय",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "महाकाव्य",
"mod_epicloot_Legendary": "प्रसिद्ध",
"mod_epicloot_Mythic": "मिथिक",
+ "mod_epicloot_Ancient": "प्राचीन",
"mod_epicloot_search_placeholder": "खोजें...",
"mod_epicloot_no_active_adventures": "कोई सक्रिय रोमांच नहीं",
"mod_epicloot_set_bonuses": "सेट बोनस",
diff --git a/EpicLoot/localizations/Hungarian.json b/EpicLoot/localizations/Hungarian.json
index 0d1d93878..275632b74 100644
--- a/EpicLoot/localizations/Hungarian.json
+++ b/EpicLoot/localizations/Hungarian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Kovakő",
"mod_epicloot_config_serpentscale": "Tengerikígyó-pikkely",
"mod_epicloot_config_triplechance": "Hármas fogás esélye",
- "mod_epicloot_config_secondspercent": "Másodperc 1 %-onként",
+ "mod_epicloot_config_secondsperpercent": "Másodperc 1 %-onként",
"mod_epicloot_config_procchance": "Aktiválási esély",
"mod_epicloot_config_bonusrollsmin": "Min. bónusz zsákmánydobás",
"mod_epicloot_config_bonusrollsmax": "Max. bónusz zsákmánydobás",
+ "mod_epicloot_config_cooldown": "Várakozás (mp)",
+ "mod_epicloot_config_basecooldown": "Alap várakozás (mp)",
+ "mod_epicloot_config_cooldownperrarity": "Várakozás ritkaságonként (mp)",
+ "mod_epicloot_config_cooldownpervalue": "Várakozás értékenként (mp)",
+ "mod_epicloot_config_buffduration": "Időtartam (mp)",
+ "mod_epicloot_config_tickinterval": "Ütemköz (mp)",
+ "mod_epicloot_config_radius": "Sugár (m)",
+ "mod_epicloot_config_baseradius": "Alapsugár (m)",
+ "mod_epicloot_config_radiuspertier": "Sugár értékenként (m)",
+ "mod_epicloot_config_maxcharges": "Találat a feltöltésig",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Találat a feltöltésig (alap)",
+ "mod_epicloot_config_chargespervalue": "Megspórolt találat értékenként",
+ "mod_epicloot_config_mincharges": "Min. találat a feltöltésig",
+ "mod_epicloot_config_mincooldown": "Min. várakozás (mp)",
+ "mod_epicloot_config_damagepertrigger": "Sebzés kiváltásonként",
+ "mod_epicloot_config_eitrpertrigger": "Eitr kiváltásonként",
+ "mod_epicloot_config_damagemultiplier": "Sebzésszorzó",
+ "mod_epicloot_config_damagefraction": "Sebzéshányad",
+ "mod_epicloot_config_damagepervalue": "Sebzés értékenként",
+ "mod_epicloot_config_frostpertier": "Fagysebzés értékenként",
+ "mod_epicloot_config_poisonpertier": "Méregsebzés értékenként",
+ "mod_epicloot_config_spawnheight": "Megjelenési magasság (m)",
+ "mod_epicloot_config_spawnradius": "Megjelenési sugár (m)",
+ "mod_epicloot_config_spawninterval": "Megjelenési időköz (mp)",
+ "mod_epicloot_config_mindistance": "Min. kilövési távolság (m)",
+ "mod_epicloot_config_maxdistance": "Max. kilövési távolság (m)",
+ "mod_epicloot_config_projectilespeed": "Lövedék sebessége (m/mp)",
+ "mod_epicloot_config_explosionradius": "Robbanási sugár (m)",
+ "mod_epicloot_config_conelength": "Kúp hossza (m)",
+ "mod_epicloot_config_conemaxwidth": "Kúp szélessége (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Kisülés figyelmen kívül hagyási ablaka (mp)",
+ "mod_epicloot_config_minmovespeed": "Min. mozgási sebesség",
+ "mod_epicloot_config_patchradius": "Folt sugara (m)",
+ "mod_epicloot_config_patchlifetime": "Folt élettartama (mp)",
+ "mod_epicloot_config_patchtickinterval": "Folt ütemköze (mp)",
+ "mod_epicloot_config_speedfloor": "Sebesség alsó határa",
+ "mod_epicloot_config_batlifetime": "Denevér élettartama (mp)",
+ "mod_epicloot_config_summoncountoffset": "Idézett darabszám eltolása",
+ "mod_epicloot_config_concurrentpersummon": "Max. egyidejű idézésenként",
+ "mod_epicloot_config_maxreduction": "Max. csökkentés",
+ "mod_epicloot_config_maxhealthpercentpervalue": "Max. életerő %-a értékenként",
+ "mod_epicloot_config_minhealthreduction": "Min. életerő-csökkentés",
+ "mod_epicloot_config_carryweightpervalue": "Teherbírás értékenként",
+ "mod_epicloot_config_minresultingcarryweight": "Min. teherbírás",
+ "mod_epicloot_config_weightthreshold": "Súlyküszöb",
+ "mod_epicloot_config_weightperbonus": "Súly bónuszonként",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-költség sebzésenként",
+ "mod_epicloot_config_minskilllevel": "Min. képességszint",
+ "mod_epicloot_config_levelweightexponent": "Szintsúlyozási kitevő",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. szintlépés kiváltásonként",
+ "mod_epicloot_config_maxmultiplier": "Max. zsákmányszorzó",
+ "mod_epicloot_config_maxunitsperentry": "Max. egység bejegyzésenként",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. hozzáadott egység ölésenként",
+ "mod_epicloot_config_rollbase": "Dobás alapja",
+ "mod_epicloot_config_rollpervalue": "Dobás értékenként",
+ "mod_epicloot_config_rollcurve": "Dobásgörbe",
+ "mod_epicloot_config_floorfraction": "Alsó határ hányada",
+ "mod_epicloot_config_chitin": "Kitin",
+ "mod_epicloot_config_copperscrap": "Rézhulladék",
+ "mod_epicloot_config_ironscrap": "Ócskavas",
+ "mod_epicloot_config_runestoneepic": "Epikus runekő",
+ "mod_epicloot_config_runestonelegendary": "Legendás runekő",
"mod_epicloot_me_modifylowhealth_config_max": "Alacsony életerő maximális küszöbe",
"mod_epicloot_me_riches_config_coins": "Érmék értéke",
"mod_epicloot_me_riches_config_amber": "Borostyán értéke",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(végleges)",
"mod_epicloot_legendary_brokkrgift": "Legendás Brokkr ajándéka",
"mod_epicloot_mythic_brokkrgift": "Mitikus Brokkr ajándéka",
+ "mod_epicloot_ancient_brokkrgift": "Ősi Brokkr ajándéka",
"mod_epicloot_slotchisel_introduce": "Rendkívül hatalmas ereklye, amelyről azt tartják, hogy bármely varázstárgyba szilánkfoglalatot illeszt.",
"mod_epicloot_slotchisel_hint": "Húzd rá egy varázstárgyra, hogy szilánkfoglalatot adj hozzá",
"mod_epicloot_slotchisel_confirm_title": "Hozzáadsz egy szilánkfoglalatot?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Minden kitérő gurulás {0} másodpercet távolít el a méreg- és égéshatásaidból.",
"mod_epicloot_me_stormrider_display": "+{0} % mozgási sebesség viharban",
"mod_epicloot_me_stormrider_desc": "+{0} % mozgási sebesség, amíg vihar tombol.",
- "mod_epicloot_me_shockingcharge_display": "Sokkoló töltet",
- "mod_epicloot_me_shockingcharge_desc": "A harci találataid töltéseket halmoznak fel. Teljes töltésnél a következő találat előrefelé villámlökéshullámot szabadít fel, amely a korábbi találataid sebzésének egy részét okozza.",
- "mod_epicloot_me_forestsaid_display": "Az erdő segítsége",
- "mod_epicloot_me_forestsaid_desc": "Amikor eltalálnak, gúzsba kötő gyökerek törnek elő, és mozgásképtelenné teszik a közeli ellenségeket.",
- "mod_epicloot_me_corpserot_display": "Hullarothadás",
- "mod_epicloot_me_corpserot_desc": "Az általad megölt ellenségek méregfelhőben robbanhatnak szét, megmérgezve a közeli ellenfeleket.",
- "mod_epicloot_me_icyretribution_display": "Jeges megtorlás",
- "mod_epicloot_me_icyretribution_desc": "Amikor eltalálnak, fagynóvát robbantasz magad körül. A visszatöltés a ritkasággal skálázódik.",
+ "mod_epicloot_me_shockingcharge_display": "Sokkoló töltet: minden {0:0.#} találat",
+ "mod_epicloot_me_shockingcharge_desc": "Minden {0:0.#} harci találat után a következő találat előrefelé villámlökéshullámot szabadít fel, amely e találatok sebzésének {1:0.#}%-át okozza. A magasabb ritkaság kevesebb találatot igényel.",
+ "mod_epicloot_me_forestsaid_display": "Az erdő segítsége: {0:0.#} m, {1:0.#} mp várakozás",
+ "mod_epicloot_me_forestsaid_desc": "Amikor eltalálnak, gúzsba kötő gyökerek törnek elő, és {0:0.#} méteres körzetben mozgásképtelenné teszik az ellenségeket. A magasabb ritkaság messzebbre ér, és {1:0.#} másodperc alatt töltődik fel.",
+ "mod_epicloot_me_corpserot_display": "Hullarothadás: {0:0.#} méreg",
+ "mod_epicloot_me_corpserot_desc": "Az általad megölt ellenségek méregfelhőben robbanhatnak szét, {0:0.#} méregsebzést okozva a közeli ellenfeleknek.",
+ "mod_epicloot_me_icyretribution_display": "Jeges megtorlás: {0:0.#} fagy",
+ "mod_epicloot_me_icyretribution_desc": "Amikor eltalálnak, fagynóvát robbantasz magad körül, amely {0:0.#} fagysebzést okoz. A magasabb ritkaság erősebben üt, és gyorsabban töltődik fel.",
"mod_epicloot_me_meteorsummoner_display": "Meteoridéző: {0}× találati sebzés",
"mod_epicloot_me_meteorsummoner_desc": "Minden {1} fegyvertalálat után a következő találatod tüzes meteort hív a célpontra, amely az adott találat sebzésének {0}-szorosát okozza tűzként.",
- "mod_epicloot_me_everflow_display": "A Királynő örök áramlása",
- "mod_epicloot_me_everflow_desc": "Egy lény megölése halmozódó bónuszt ad, amely növeli az életerő-, állóképesség- és eitr-regenerációdat.",
- "mod_epicloot_me_necroticfire_display": "Nekrotikus tűz",
- "mod_epicloot_me_necroticfire_desc": "Eltávolítja a fegyveredről az összes fizikai sebzést, és ezt a teljes értéket tűz- és méregsebzésként is hozzáadja.",
+ "mod_epicloot_me_everflow_display": "A Királynő örök áramlása: +{0:0.#}% regeneráció rétegenként",
+ "mod_epicloot_me_everflow_desc": "Egy lény megölése halmozódó bónuszt ad, amely rétegenként {0:0.#}%-kal növeli az életerő-, állóképesség- és eitr-regenerációdat, legfeljebb {1:0.#} rétegig.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotikus tűz: +{0:0.#}% tűz és méreg",
+ "mod_epicloot_me_necroticfire_desc": "Eltávolítja a fegyveredről az összes fizikai sebzést, és annak {0:0.#}%-át tűzsebzésként, ugyanennyit pedig méregsebzésként ad hozzá.",
"mod_epicloot_circlet": "fejdísz",
"mod_epicloot_belt": "öv",
"mod_epicloot_remains": "maradványok",
@@ -1021,7 +1084,7 @@
"mod_epicloot_bounties_suffix_svalroot": "Svalroot",
"mod_epicloot_bounties_suffix_swiftspite": "Swiftspite",
"mod_epicloot_bounties_suffix_the_abandoned": "the Abandoned",
- "mod_epicloot_bounties_suffix_the_ancient": "the Ancient",
+ "mod_epicloot_bounties_suffix_the_ancient": "az Ősi",
"mod_epicloot_bounties_suffix_the_betrayer": "the Betrayer",
"mod_epicloot_bounties_suffix_the_brutal": "the Brutal",
"mod_epicloot_bounties_suffix_the_brute": "the Brute",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendás {0}",
"mod_epicloot_basicmythicnameformat": "Mítikus {0}",
+ "mod_epicloot_basicancientnameformat": "Ősi {0}",
"mod_epicloot_rare_prefix1": "Furcsa",
"mod_epicloot_rare_prefix2": "Ritka",
"mod_epicloot_rare_prefix3": "Titokzatos",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epikus",
"mod_epicloot_Legendary": "Legendás",
"mod_epicloot_Mythic": "mitikus",
+ "mod_epicloot_Ancient": "Ősi",
"mod_epicloot_search_placeholder": "Keresés...",
"mod_epicloot_no_active_adventures": "Nincs aktív kaland",
"mod_epicloot_set_bonuses": "Szett bónuszok",
diff --git a/EpicLoot/localizations/Icelandic.json b/EpicLoot/localizations/Icelandic.json
index 53fb36324..940a63b9c 100644
--- a/EpicLoot/localizations/Icelandic.json
+++ b/EpicLoot/localizations/Icelandic.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Tinna",
"mod_epicloot_config_serpentscale": "Sæormshreistur",
"mod_epicloot_config_triplechance": "Líkur á þreföldum afla",
- "mod_epicloot_config_secondspercent": "Sekúndur á hvert 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekúndur á hvert 1 %",
"mod_epicloot_config_procchance": "Virkjunarlíkur",
"mod_epicloot_config_bonusrollsmin": "Lágm. aukaköst fyrir herfang",
"mod_epicloot_config_bonusrollsmax": "Hám. aukaköst fyrir herfang",
+ "mod_epicloot_config_cooldown": "Bið (s)",
+ "mod_epicloot_config_basecooldown": "Grunnbið (s)",
+ "mod_epicloot_config_cooldownperrarity": "Bið eftir sjaldgæfni (s)",
+ "mod_epicloot_config_cooldownpervalue": "Bið eftir gildi (s)",
+ "mod_epicloot_config_buffduration": "Lengd (s)",
+ "mod_epicloot_config_tickinterval": "Tifbil (s)",
+ "mod_epicloot_config_radius": "Radíus (m)",
+ "mod_epicloot_config_baseradius": "Grunnradíus (m)",
+ "mod_epicloot_config_radiuspertier": "Radíus eftir gildi (m)",
+ "mod_epicloot_config_maxcharges": "Högg að hleðslu",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Högg að hleðslu (grunn)",
+ "mod_epicloot_config_chargespervalue": "Spöruð högg eftir gildi",
+ "mod_epicloot_config_mincharges": "Lágm. högg að hleðslu",
+ "mod_epicloot_config_mincooldown": "Lágm. bið (s)",
+ "mod_epicloot_config_damagepertrigger": "Skaði á hverja virkjun",
+ "mod_epicloot_config_eitrpertrigger": "Eitr á hverja virkjun",
+ "mod_epicloot_config_damagemultiplier": "Skaðamargfaldari",
+ "mod_epicloot_config_damagefraction": "Skaðahlutfall",
+ "mod_epicloot_config_damagepervalue": "Skaði eftir gildi",
+ "mod_epicloot_config_frostpertier": "Frostskaði eftir gildi",
+ "mod_epicloot_config_poisonpertier": "Eiturskaði eftir gildi",
+ "mod_epicloot_config_spawnheight": "Birtingarhæð (m)",
+ "mod_epicloot_config_spawnradius": "Birtingarradíus (m)",
+ "mod_epicloot_config_spawninterval": "Birtingarbil (s)",
+ "mod_epicloot_config_mindistance": "Lágm. skotfjarlægð (m)",
+ "mod_epicloot_config_maxdistance": "Hám. skotfjarlægð (m)",
+ "mod_epicloot_config_projectilespeed": "Hraði skeytis (m/s)",
+ "mod_epicloot_config_explosionradius": "Sprengiradíus (m)",
+ "mod_epicloot_config_conelength": "Lengd keilu (m)",
+ "mod_epicloot_config_conemaxwidth": "Breidd keilu (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Hunsunargluggi afhleðslu (s)",
+ "mod_epicloot_config_minmovespeed": "Lágm. hreyfihraði",
+ "mod_epicloot_config_patchradius": "Radíus svæðis (m)",
+ "mod_epicloot_config_patchlifetime": "Líftími svæðis (s)",
+ "mod_epicloot_config_patchtickinterval": "Tifbil svæðis (s)",
+ "mod_epicloot_config_speedfloor": "Lágmarkshraði",
+ "mod_epicloot_config_batlifetime": "Líftími leðurblöku (s)",
+ "mod_epicloot_config_summoncountoffset": "Hliðrun á fjölda kallaðra",
+ "mod_epicloot_config_concurrentpersummon": "Hám. samtímis á hverja köllun",
+ "mod_epicloot_config_maxreduction": "Hám. minnkun",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% af hám. heilsu eftir gildi",
+ "mod_epicloot_config_minhealthreduction": "Lágm. heilsuminnkun",
+ "mod_epicloot_config_carryweightpervalue": "Burðargeta eftir gildi",
+ "mod_epicloot_config_minresultingcarryweight": "Lágm. burðargeta",
+ "mod_epicloot_config_weightthreshold": "Þyngdarmörk",
+ "mod_epicloot_config_weightperbonus": "Þyngd á hvern bónus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitrkostnaður á hvern skaða",
+ "mod_epicloot_config_minskilllevel": "Lágm. færnistig",
+ "mod_epicloot_config_levelweightexponent": "Veldisvísir stigavægis",
+ "mod_epicloot_config_maxlevelupsperproc": "Hám. stighækkanir á hverja virkjun",
+ "mod_epicloot_config_maxmultiplier": "Hám. herfangsmargfaldari",
+ "mod_epicloot_config_maxunitsperentry": "Hám. einingar á hverja færslu",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Hám. viðbættar einingar á hvert dráp",
+ "mod_epicloot_config_rollbase": "Grunnkast",
+ "mod_epicloot_config_rollpervalue": "Kast eftir gildi",
+ "mod_epicloot_config_rollcurve": "Kastferill",
+ "mod_epicloot_config_floorfraction": "Hlutfall lágmarks",
+ "mod_epicloot_config_chitin": "Kítín",
+ "mod_epicloot_config_copperscrap": "Koparafgangar",
+ "mod_epicloot_config_ironscrap": "Járnbútur",
+ "mod_epicloot_config_runestoneepic": "Epískur rúnasteinn",
+ "mod_epicloot_config_runestonelegendary": "Frægur rúnasteinn",
"mod_epicloot_me_modifylowhealth_config_max": "Hámarksmörk fyrir lága heilsu",
"mod_epicloot_me_riches_config_coins": "Myntverðmæti",
"mod_epicloot_me_riches_config_amber": "Rafverðmæti",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(varanlegt)",
"mod_epicloot_legendary_brokkrgift": "Fræg gjöf Brokks",
"mod_epicloot_mythic_brokkrgift": "Mýtísk gjöf Brokks",
+ "mod_epicloot_ancient_brokkrgift": "Forn gjöf Brokks",
"mod_epicloot_slotchisel_introduce": "Ákaflega máttugur forngripur sem sagður er fella brotarauf í hvaða galdrahlut sem er.",
"mod_epicloot_slotchisel_hint": "Dragðu hann á galdrahlut til að bæta við brotarauf",
"mod_epicloot_slotchisel_confirm_title": "Bæta við brotarauf?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Hver vikningarvelta fjarlægir {0} sekúndur af eitur- og brunaáhrifum þínum.",
"mod_epicloot_me_stormrider_display": "+{0} % hreyfihraði í stormum",
"mod_epicloot_me_stormrider_desc": "+{0} % hreyfihraði meðan stormur geisar.",
- "mod_epicloot_me_shockingcharge_display": "Lostandi hleðsla",
- "mod_epicloot_me_shockingcharge_desc": "Bardagahögg þín safna hleðslum. Við fulla hleðslu leysir næsta högg úr læðingi eldingahöggbylgju fram á við sem veitir hluta af skaða fyrri högga þinna.",
- "mod_epicloot_me_forestsaid_display": "Hjálp skógarins",
- "mod_epicloot_me_forestsaid_desc": "Þegar þú verður fyrir höggi brjótast fram flæktar rætur sem festa óvini nálægt.",
- "mod_epicloot_me_corpserot_display": "Náfúi",
- "mod_epicloot_me_corpserot_desc": "Óvinir sem þú drepur geta sprungið í eiturskýi og eitrað fyrir andstæðingum nálægt.",
- "mod_epicloot_me_icyretribution_display": "Ísköld hefnd",
- "mod_epicloot_me_icyretribution_desc": "Þegar þú verður fyrir höggi sprengirðu frostnóvu í kringum þig. Biðtíminn skalast með fágæti.",
+ "mod_epicloot_me_shockingcharge_display": "Lostandi hleðsla: á hverjum {0:0.#} höggum",
+ "mod_epicloot_me_shockingcharge_desc": "Á hverjum {0:0.#} bardagahöggum leysir næsta högg úr læðingi eldingahöggbylgju fram á við sem veitir {1:0.#}% af skaða þeirra högga. Meiri fágæti krefst færri högga.",
+ "mod_epicloot_me_forestsaid_display": "Hjálp skógarins: {0:0.#} m, {1:0.#} s bið",
+ "mod_epicloot_me_forestsaid_desc": "Þegar þú verður fyrir höggi brjótast fram flæktar rætur sem festa óvini innan {0:0.#} metra. Meiri fágæti nær lengra og hleðst á {1:0.#} sekúndum.",
+ "mod_epicloot_me_corpserot_display": "Náfúi: {0:0.#} eitur",
+ "mod_epicloot_me_corpserot_desc": "Óvinir sem þú drepur geta sprungið í eiturskýi og veitt andstæðingum nálægt {0:0.#} eiturskaða.",
+ "mod_epicloot_me_icyretribution_display": "Ísköld hefnd: {0:0.#} frost",
+ "mod_epicloot_me_icyretribution_desc": "Þegar þú verður fyrir höggi sprengirðu frostnóvu í kringum þig sem veitir {0:0.#} frostskaða. Meiri fágæti slær fastar og hleðst hraðar.",
"mod_epicloot_me_meteorsummoner_display": "Loftsteinakallari: {0}× höggskaði",
"mod_epicloot_me_meteorsummoner_desc": "Á hverjum {1} vopnahöggum kallar næsta högg þitt logandi loftstein niður á skotmarkið sem veitir {0}× skaða þess höggs sem eld.",
- "mod_epicloot_me_everflow_display": "Eilíft streymi Drottningarinnar",
- "mod_epicloot_me_everflow_desc": "Að drepa skepnu veitir staflanleg áhrif sem auka heilsu-, þreks- og eitrendurnýjun þína.",
- "mod_epicloot_me_necroticfire_display": "Drepandi eldur",
- "mod_epicloot_me_necroticfire_desc": "Sviptir vopnið þitt öllum líkamlegum skaða og bætir þeirri fullu upphæð við bæði sem eld- og eiturskaða.",
+ "mod_epicloot_me_everflow_display": "Eilíft streymi Drottningarinnar: +{0:0.#}% endurnýjun á stafla",
+ "mod_epicloot_me_everflow_desc": "Að drepa skepnu veitir staflanleg áhrif sem auka heilsu-, þreks- og eitrendurnýjun þína um {0:0.#}% á stafla, allt að {1:0.#} stöflum.",
+ "mod_epicloot_me_necroticfire_display": "Drepandi eldur: +{0:0.#}% eldur og eitur",
+ "mod_epicloot_me_necroticfire_desc": "Sviptir vopnið þitt öllum líkamlegum skaða og bætir {0:0.#}% af honum við sem eldskaða og jafn miklu sem eiturskaða.",
"mod_epicloot_circlet": "hringur",
"mod_epicloot_belt": "belti",
"mod_epicloot_remains": "leifar",
@@ -1021,7 +1084,7 @@
"mod_epicloot_bounties_suffix_svalroot": "Svalroot",
"mod_epicloot_bounties_suffix_swiftspite": "Swiftspite",
"mod_epicloot_bounties_suffix_the_abandoned": "the Abandoned",
- "mod_epicloot_bounties_suffix_the_ancient": "the Ancient",
+ "mod_epicloot_bounties_suffix_the_ancient": "Forni",
"mod_epicloot_bounties_suffix_the_betrayer": "the Betrayer",
"mod_epicloot_bounties_suffix_the_brutal": "the Brutal",
"mod_epicloot_bounties_suffix_the_brute": "the Brute",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendary {0}",
"mod_epicloot_basicmythicnameformat": "Mythic {0}",
+ "mod_epicloot_basicancientnameformat": "Forn {0}",
"mod_epicloot_rare_prefix1": "Undarlegur",
"mod_epicloot_rare_prefix2": "Sjaldgæfur",
"mod_epicloot_rare_prefix3": "Dulmálugur",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epískt",
"mod_epicloot_Legendary": "Frægur",
"mod_epicloot_Mythic": " Mýtískt",
+ "mod_epicloot_Ancient": "Forn",
"mod_epicloot_search_placeholder": "Leita...",
"mod_epicloot_no_active_adventures": "Engar virkar ævintýri",
"mod_epicloot_set_bonuses": "Settafbótir",
diff --git a/EpicLoot/localizations/Italian.json b/EpicLoot/localizations/Italian.json
index 958246418..cee61a795 100644
--- a/EpicLoot/localizations/Italian.json
+++ b/EpicLoot/localizations/Italian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Selce",
"mod_epicloot_config_serpentscale": "Scaglia di serpente",
"mod_epicloot_config_triplechance": "Probabilità di tripla presa",
- "mod_epicloot_config_secondspercent": "Secondi per 1 %",
+ "mod_epicloot_config_secondsperpercent": "Secondi per 1 %",
"mod_epicloot_config_procchance": "Probabilità di attivazione",
"mod_epicloot_config_bonusrollsmin": "Tiri bottino bonus min.",
"mod_epicloot_config_bonusrollsmax": "Tiri bottino bonus max.",
+ "mod_epicloot_config_cooldown": "Ricarica (s)",
+ "mod_epicloot_config_basecooldown": "Ricarica base (s)",
+ "mod_epicloot_config_cooldownperrarity": "Ricarica per rarità (s)",
+ "mod_epicloot_config_cooldownpervalue": "Ricarica per valore (s)",
+ "mod_epicloot_config_buffduration": "Durata (s)",
+ "mod_epicloot_config_tickinterval": "Intervallo di tick (s)",
+ "mod_epicloot_config_radius": "Raggio (m)",
+ "mod_epicloot_config_baseradius": "Raggio base (m)",
+ "mod_epicloot_config_radiuspertier": "Raggio per valore (m)",
+ "mod_epicloot_config_maxcharges": "Colpi alla carica",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Colpi alla carica (base)",
+ "mod_epicloot_config_chargespervalue": "Colpi risparmiati per valore",
+ "mod_epicloot_config_mincharges": "Colpi min. alla carica",
+ "mod_epicloot_config_mincooldown": "Ricarica min. (s)",
+ "mod_epicloot_config_damagepertrigger": "Danno per attivazione",
+ "mod_epicloot_config_eitrpertrigger": "Eitr per attivazione",
+ "mod_epicloot_config_damagemultiplier": "Moltiplicatore di danno",
+ "mod_epicloot_config_damagefraction": "Frazione di danno",
+ "mod_epicloot_config_damagepervalue": "Danno per valore",
+ "mod_epicloot_config_frostpertier": "Danno da gelo per valore",
+ "mod_epicloot_config_poisonpertier": "Danno da veleno per valore",
+ "mod_epicloot_config_spawnheight": "Altezza di comparsa (m)",
+ "mod_epicloot_config_spawnradius": "Raggio di comparsa (m)",
+ "mod_epicloot_config_spawninterval": "Intervallo di comparsa (s)",
+ "mod_epicloot_config_mindistance": "Distanza di lancio min. (m)",
+ "mod_epicloot_config_maxdistance": "Distanza di lancio max. (m)",
+ "mod_epicloot_config_projectilespeed": "Velocità del proiettile (m/s)",
+ "mod_epicloot_config_explosionradius": "Raggio d'esplosione (m)",
+ "mod_epicloot_config_conelength": "Lunghezza del cono (m)",
+ "mod_epicloot_config_conemaxwidth": "Larghezza del cono (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Finestra di ignoro della scarica (s)",
+ "mod_epicloot_config_minmovespeed": "Velocità di movimento min.",
+ "mod_epicloot_config_patchradius": "Raggio dell'area (m)",
+ "mod_epicloot_config_patchlifetime": "Durata dell'area (s)",
+ "mod_epicloot_config_patchtickinterval": "Intervallo di tick dell'area (s)",
+ "mod_epicloot_config_speedfloor": "Velocità minima",
+ "mod_epicloot_config_batlifetime": "Durata del pipistrello (s)",
+ "mod_epicloot_config_summoncountoffset": "Scarto del numero di evocazioni",
+ "mod_epicloot_config_concurrentpersummon": "Max. simultanei per evocazione",
+ "mod_epicloot_config_maxreduction": "Riduzione max.",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% di salute max. per valore",
+ "mod_epicloot_config_minhealthreduction": "Riduzione di salute min.",
+ "mod_epicloot_config_carryweightpervalue": "Capacità di carico per valore",
+ "mod_epicloot_config_minresultingcarryweight": "Capacità di carico min.",
+ "mod_epicloot_config_weightthreshold": "Soglia di peso",
+ "mod_epicloot_config_weightperbonus": "Peso per bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Costo in eitr per danno",
+ "mod_epicloot_config_minskilllevel": "Livello di abilità min.",
+ "mod_epicloot_config_levelweightexponent": "Esponente di peso del livello",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. aumenti di livello per attivazione",
+ "mod_epicloot_config_maxmultiplier": "Moltiplicatore di bottino max.",
+ "mod_epicloot_config_maxunitsperentry": "Unità max. per voce",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Unità aggiunte max. per uccisione",
+ "mod_epicloot_config_rollbase": "Base del tiro",
+ "mod_epicloot_config_rollpervalue": "Tiro per valore",
+ "mod_epicloot_config_rollcurve": "Curva del tiro",
+ "mod_epicloot_config_floorfraction": "Frazione minima",
+ "mod_epicloot_config_chitin": "Chitina",
+ "mod_epicloot_config_copperscrap": "Scarti di rame",
+ "mod_epicloot_config_ironscrap": "Ferraglia",
+ "mod_epicloot_config_runestoneepic": "Pietra runica epica",
+ "mod_epicloot_config_runestonelegendary": "Pietra runica leggendaria",
"mod_epicloot_me_modifylowhealth_config_max": "Soglia massima di salute bassa",
"mod_epicloot_me_riches_config_coins": "Valore delle monete",
"mod_epicloot_me_riches_config_amber": "Valore dell'ambra",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanente)",
"mod_epicloot_legendary_brokkrgift": "Dono di Brokkr leggendario",
"mod_epicloot_mythic_brokkrgift": "Dono di Brokkr mitico",
+ "mod_epicloot_ancient_brokkrgift": "Dono di Brokkr antico",
"mod_epicloot_slotchisel_introduce": "Un artefatto straordinariamente potente, che si dice incastoni un alloggiamento per schegge in qualsiasi oggetto magico.",
"mod_epicloot_slotchisel_hint": "Trascinalo su un oggetto magico per aggiungere un alloggiamento per schegge",
"mod_epicloot_slotchisel_confirm_title": "Aggiungere un alloggiamento per schegge?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Ogni capriola di schivata rimuove {0} secondi dai tuoi effetti di veleno e bruciatura.",
"mod_epicloot_me_stormrider_display": "+{0} % di velocità di movimento nelle tempeste",
"mod_epicloot_me_stormrider_desc": "+{0} % di velocità di movimento mentre infuria una tempesta.",
- "mod_epicloot_me_shockingcharge_display": "Carica folgorante",
- "mod_epicloot_me_shockingcharge_desc": "I tuoi colpi in combattimento accumulano cariche. Al massimo della carica, il colpo successivo scatena un'onda d'urto elettrica in avanti che infligge una parte del danno dei tuoi colpi precedenti.",
- "mod_epicloot_me_forestsaid_display": "Aiuto della foresta",
- "mod_epicloot_me_forestsaid_desc": "Quando vieni colpito, radici avviluppanti erompono e immobilizzano i nemici vicini.",
- "mod_epicloot_me_corpserot_display": "Putrefazione cadaverica",
- "mod_epicloot_me_corpserot_desc": "I nemici che uccidi possono esplodere in una nube di veleno, avvelenando gli avversari vicini.",
- "mod_epicloot_me_icyretribution_display": "Rappresaglia gelida",
- "mod_epicloot_me_icyretribution_desc": "Quando vieni colpito, fai detonare una nova di gelo attorno a te. Il recupero scala con la rarità.",
+ "mod_epicloot_me_shockingcharge_display": "Carica folgorante: ogni {0:0.#} colpi",
+ "mod_epicloot_me_shockingcharge_desc": "Ogni {0:0.#} colpi in combattimento, il colpo successivo scatena un'onda d'urto elettrica in avanti che infligge il {1:0.#}% del danno di quei colpi. Una rarità superiore richiede meno colpi.",
+ "mod_epicloot_me_forestsaid_display": "Aiuto della foresta: {0:0.#} m, {1:0.#} s di ricarica",
+ "mod_epicloot_me_forestsaid_desc": "Quando vieni colpito, radici avviluppanti erompono e immobilizzano i nemici entro {0:0.#} metri. Una rarità superiore arriva più lontano e si ricarica in {1:0.#} secondi.",
+ "mod_epicloot_me_corpserot_display": "Putrefazione cadaverica: {0:0.#} veleno",
+ "mod_epicloot_me_corpserot_desc": "I nemici che uccidi possono esplodere in una nube di veleno, infliggendo {0:0.#} danni da veleno agli avversari vicini.",
+ "mod_epicloot_me_icyretribution_display": "Rappresaglia gelida: {0:0.#} gelo",
+ "mod_epicloot_me_icyretribution_desc": "Quando vieni colpito, fai detonare una nova di gelo attorno a te che infligge {0:0.#} danni da gelo. Una rarità superiore colpisce più forte e si ricarica prima.",
"mod_epicloot_me_meteorsummoner_display": "Evocatore di meteore: ×{0} del danno del colpo",
"mod_epicloot_me_meteorsummoner_desc": "Ogni {1} colpi d'arma, il tuo colpo successivo fa piombare una meteora infuocata sul bersaglio che infligge {0} volte il danno di quel colpo come fuoco.",
- "mod_epicloot_me_everflow_display": "Flusso eterno della Regina",
- "mod_epicloot_me_everflow_desc": "Uccidere una creatura concede un potenziamento cumulabile che aumenta la rigenerazione di salute, stamina ed eitr.",
- "mod_epicloot_me_necroticfire_display": "Fuoco necrotico",
- "mod_epicloot_me_necroticfire_desc": "Rimuove tutto il danno fisico dalla tua arma e aggiunge quell'intero ammontare sia come danno da fuoco sia come danno da veleno.",
+ "mod_epicloot_me_everflow_display": "Flusso eterno della Regina: +{0:0.#}% di rigenerazione per accumulo",
+ "mod_epicloot_me_everflow_desc": "Uccidere una creatura concede un potenziamento cumulabile che aumenta la rigenerazione di salute, stamina ed eitr del {0:0.#}% per accumulo, fino a {1:0.#} accumuli.",
+ "mod_epicloot_me_necroticfire_display": "Fuoco necrotico: +{0:0.#}% fuoco e veleno",
+ "mod_epicloot_me_necroticfire_desc": "Rimuove tutto il danno fisico dalla tua arma e ne aggiunge il {0:0.#}% come danno da fuoco e altrettanto come danno da veleno.",
"mod_epicloot_circlet": "diadema",
"mod_epicloot_belt": "cintura",
"mod_epicloot_remains": "resti",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Leggendario {0}",
"mod_epicloot_basicmythicnameformat": "Mitico {0}",
+ "mod_epicloot_basicancientnameformat": "Antico {0}",
"mod_epicloot_rare_prefix1": "Strano",
"mod_epicloot_rare_prefix2": "Raro",
"mod_epicloot_rare_prefix3": "Criptico",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "o",
"mod_epicloot_Legendary": "Leggendario",
"mod_epicloot_Mythic": " Mito",
+ "mod_epicloot_Ancient": "Antico",
"mod_epicloot_search_placeholder": "Cerca...",
"mod_epicloot_no_active_adventures": "Nessuna avventura attiva",
"mod_epicloot_set_bonuses": "Bonus set",
diff --git a/EpicLoot/localizations/Japanese.json b/EpicLoot/localizations/Japanese.json
index 66ce0a36e..c0938a65d 100644
--- a/EpicLoot/localizations/Japanese.json
+++ b/EpicLoot/localizations/Japanese.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "火打ち石",
"mod_epicloot_config_serpentscale": "シーサーペントの鱗",
"mod_epicloot_config_triplechance": "三重釣果の確率",
- "mod_epicloot_config_secondspercent": "1 % あたりの秒数",
+ "mod_epicloot_config_secondsperpercent": "1 % あたりの秒数",
"mod_epicloot_config_procchance": "発動確率",
"mod_epicloot_config_bonusrollsmin": "最小ボーナス戦利品抽選",
"mod_epicloot_config_bonusrollsmax": "最大ボーナス戦利品抽選",
+ "mod_epicloot_config_cooldown": "クールダウン(秒)",
+ "mod_epicloot_config_basecooldown": "基本クールダウン(秒)",
+ "mod_epicloot_config_cooldownperrarity": "希少度ごとのクールダウン(秒)",
+ "mod_epicloot_config_cooldownpervalue": "数値ごとのクールダウン(秒)",
+ "mod_epicloot_config_buffduration": "効果時間(秒)",
+ "mod_epicloot_config_tickinterval": "ティック間隔(秒)",
+ "mod_epicloot_config_radius": "半径(m)",
+ "mod_epicloot_config_baseradius": "基本半径(m)",
+ "mod_epicloot_config_radiuspertier": "数値ごとの半径(m)",
+ "mod_epicloot_config_maxcharges": "充電までの命中数",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "充電までの命中数(基本)",
+ "mod_epicloot_config_chargespervalue": "数値ごとの短縮命中数",
+ "mod_epicloot_config_mincharges": "充電までの最小命中数",
+ "mod_epicloot_config_mincooldown": "最小クールダウン(秒)",
+ "mod_epicloot_config_damagepertrigger": "発動ごとのダメージ",
+ "mod_epicloot_config_eitrpertrigger": "発動ごとのエイトル",
+ "mod_epicloot_config_damagemultiplier": "ダメージ倍率",
+ "mod_epicloot_config_damagefraction": "ダメージ割合",
+ "mod_epicloot_config_damagepervalue": "数値ごとのダメージ",
+ "mod_epicloot_config_frostpertier": "数値ごとの氷ダメージ",
+ "mod_epicloot_config_poisonpertier": "数値ごとの毒ダメージ",
+ "mod_epicloot_config_spawnheight": "出現高度(m)",
+ "mod_epicloot_config_spawnradius": "出現半径(m)",
+ "mod_epicloot_config_spawninterval": "出現間隔(秒)",
+ "mod_epicloot_config_mindistance": "最小射出距離(m)",
+ "mod_epicloot_config_maxdistance": "最大射出距離(m)",
+ "mod_epicloot_config_projectilespeed": "投射物速度(m/秒)",
+ "mod_epicloot_config_explosionradius": "爆発半径(m)",
+ "mod_epicloot_config_conelength": "円錐の長さ(m)",
+ "mod_epicloot_config_conemaxwidth": "円錐の幅(m)",
+ "mod_epicloot_config_dischargeignorewindow": "放電無視時間(秒)",
+ "mod_epicloot_config_minmovespeed": "最小移動速度",
+ "mod_epicloot_config_patchradius": "範囲の半径(m)",
+ "mod_epicloot_config_patchlifetime": "範囲の持続時間(秒)",
+ "mod_epicloot_config_patchtickinterval": "範囲のティック間隔(秒)",
+ "mod_epicloot_config_speedfloor": "速度の下限",
+ "mod_epicloot_config_batlifetime": "コウモリの持続時間(秒)",
+ "mod_epicloot_config_summoncountoffset": "召喚数のオフセット",
+ "mod_epicloot_config_concurrentpersummon": "召喚ごとの最大同時数",
+ "mod_epicloot_config_maxreduction": "最大軽減",
+ "mod_epicloot_config_maxhealthpercentpervalue": "数値ごとの最大体力%",
+ "mod_epicloot_config_minhealthreduction": "最小体力減少",
+ "mod_epicloot_config_carryweightpervalue": "数値ごとの所持重量",
+ "mod_epicloot_config_minresultingcarryweight": "最小所持重量",
+ "mod_epicloot_config_weightthreshold": "重量しきい値",
+ "mod_epicloot_config_weightperbonus": "ボーナスごとの重量",
+ "mod_epicloot_config_eitrcostperdamage": "ダメージごとのエイトル消費",
+ "mod_epicloot_config_minskilllevel": "最小スキルレベル",
+ "mod_epicloot_config_levelweightexponent": "レベル重み指数",
+ "mod_epicloot_config_maxlevelupsperproc": "発動ごとの最大レベルアップ数",
+ "mod_epicloot_config_maxmultiplier": "最大ドロップ倍率",
+ "mod_epicloot_config_maxunitsperentry": "エントリごとの最大個数",
+ "mod_epicloot_config_maxaddedunitsperdeath": "撃破ごとの最大追加個数",
+ "mod_epicloot_config_rollbase": "抽選の基礎値",
+ "mod_epicloot_config_rollpervalue": "数値ごとの抽選",
+ "mod_epicloot_config_rollcurve": "抽選カーブ",
+ "mod_epicloot_config_floorfraction": "下限の割合",
+ "mod_epicloot_config_chitin": "キチン",
+ "mod_epicloot_config_copperscrap": "銅の破片",
+ "mod_epicloot_config_ironscrap": "クズ鉄",
+ "mod_epicloot_config_runestoneepic": "叙事詩的ルーンストーン",
+ "mod_epicloot_config_runestonelegendary": "伝説のルーンストーン",
"mod_epicloot_me_modifylowhealth_config_max": "低体力の最大しきい値",
"mod_epicloot_me_riches_config_coins": "コインの価値",
"mod_epicloot_me_riches_config_amber": "琥珀の価値",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(恒久)",
"mod_epicloot_legendary_brokkrgift": "伝説のブロックの贈り物",
"mod_epicloot_mythic_brokkrgift": "神話のブロックの贈り物",
+ "mod_epicloot_ancient_brokkrgift": "古代のブロックの贈り物",
"mod_epicloot_slotchisel_introduce": "きわめて強力なアーティファクト。どんな魔法アイテムにも欠片スロットを埋め込むと言われている。",
"mod_epicloot_slotchisel_hint": "魔法アイテムにドラッグして欠片スロットを追加",
"mod_epicloot_slotchisel_confirm_title": "欠片スロットを追加しますか?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "回避ローリングごとに、毒と炎上の効果から {0} 秒を取り除きます。",
"mod_epicloot_me_stormrider_display": "嵐の中で移動速度 +{0} %",
"mod_epicloot_me_stormrider_desc": "嵐が吹き荒れている間、移動速度 +{0} %。",
- "mod_epicloot_me_shockingcharge_display": "衝撃の充電",
- "mod_epicloot_me_shockingcharge_desc": "戦闘での命中がチャージを蓄積します。最大チャージ時、次の一撃が前方に雷の衝撃波を放ち、それまでの命中ダメージの一部を与えます。",
- "mod_epicloot_me_forestsaid_display": "森の助け",
- "mod_epicloot_me_forestsaid_desc": "被弾すると、絡みつく根が噴き出して近くの敵を拘束します。",
- "mod_epicloot_me_corpserot_display": "死骸の腐敗",
- "mod_epicloot_me_corpserot_desc": "倒した敵が毒の雲となって弾け、近くの敵を毒に侵すことがあります。",
- "mod_epicloot_me_icyretribution_display": "氷の報復",
- "mod_epicloot_me_icyretribution_desc": "被弾すると、自分の周囲にフロストノヴァを発生させます。クールダウンはレア度に応じて変わります。",
+ "mod_epicloot_me_shockingcharge_display": "衝撃の充電:{0:0.#} 回命中ごと",
+ "mod_epicloot_me_shockingcharge_desc": "戦闘で {0:0.#} 回命中するごとに、次の一撃が前方に雷の衝撃波を放ち、それらの命中ダメージの {1:0.#}% を与えます。レアリティが高いほど必要な命中回数が減ります。",
+ "mod_epicloot_me_forestsaid_display": "森の助け:{0:0.#} m、クールダウン {1:0.#} 秒",
+ "mod_epicloot_me_forestsaid_desc": "被弾すると、絡みつく根が噴き出して {0:0.#} メートル以内の敵を拘束します。レアリティが高いほど範囲が広がり、クールダウンは {1:0.#} 秒です。",
+ "mod_epicloot_me_corpserot_display": "死骸の腐敗:{0:0.#} 毒",
+ "mod_epicloot_me_corpserot_desc": "倒した敵が毒の雲となって弾け、近くの敵に {0:0.#} の毒ダメージを与えることがあります。",
+ "mod_epicloot_me_icyretribution_display": "氷の報復:{0:0.#} 氷",
+ "mod_epicloot_me_icyretribution_desc": "被弾すると、自分の周囲にフロストノヴァを発生させ {0:0.#} の氷ダメージを与えます。レアリティが高いほど威力が上がり、クールダウンも短くなります。",
"mod_epicloot_me_meteorsummoner_display": "隕石召喚者: 命中ダメージの {0} 倍",
"mod_epicloot_me_meteorsummoner_desc": "武器で {1} 回命中するごとに、次の一撃が対象に炎の隕石を落とし、その一撃のダメージの {0} 倍を火ダメージとして与えます。",
- "mod_epicloot_me_everflow_display": "女王の永久の流れ",
- "mod_epicloot_me_everflow_desc": "クリーチャーを倒すと、体力・スタミナ・エイトルの回復を高める重複バフを得ます。",
- "mod_epicloot_me_necroticfire_display": "壊死の炎",
- "mod_epicloot_me_necroticfire_desc": "武器から物理ダメージをすべて取り除き、その全量を火ダメージと毒ダメージの両方として追加します。",
+ "mod_epicloot_me_everflow_display": "女王の永久の流れ:1スタックにつき回復 +{0:0.#}%",
+ "mod_epicloot_me_everflow_desc": "クリーチャーを倒すと、体力・スタミナ・エイトルの回復を1スタックにつき {0:0.#}% 高める重複バフを得ます(最大 {1:0.#} スタック)。",
+ "mod_epicloot_me_necroticfire_display": "壊死の炎:火と毒 +{0:0.#}%",
+ "mod_epicloot_me_necroticfire_desc": "武器から物理ダメージをすべて取り除き、その {0:0.#}% を火ダメージとして、さらに同量を毒ダメージとして追加します。",
"mod_epicloot_circlet": "シルクレット",
"mod_epicloot_belt": "ベルト",
"mod_epicloot_remains": "遺物",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "伝説の{0}",
"mod_epicloot_basicmythicnameformat": "神話の{0}",
+ "mod_epicloot_basicancientnameformat": "古代の{0}",
"mod_epicloot_rare_prefix1": "不思議な",
"mod_epicloot_rare_prefix2": "珍しい",
"mod_epicloot_rare_prefix3": "秘密の",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "叙事詩的",
"mod_epicloot_Legendary": "伝説的な",
"mod_epicloot_Mythic": "神話的神話的",
+ "mod_epicloot_Ancient": "古代",
"mod_epicloot_search_placeholder": "検索...",
"mod_epicloot_no_active_adventures": "アクティブな冒険はありません",
"mod_epicloot_set_bonuses": "セットボーナス",
diff --git a/EpicLoot/localizations/Korean.json b/EpicLoot/localizations/Korean.json
index 4dfd659b3..4d1389a58 100644
--- a/EpicLoot/localizations/Korean.json
+++ b/EpicLoot/localizations/Korean.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "부싯돌",
"mod_epicloot_config_serpentscale": "바다뱀 비늘",
"mod_epicloot_config_triplechance": "삼중 어획 확률",
- "mod_epicloot_config_secondspercent": "1 %당 초",
+ "mod_epicloot_config_secondsperpercent": "1 %당 초",
"mod_epicloot_config_procchance": "발동 확률",
"mod_epicloot_config_bonusrollsmin": "최소 보너스 전리품 굴림",
"mod_epicloot_config_bonusrollsmax": "최대 보너스 전리품 굴림",
+ "mod_epicloot_config_cooldown": "재사용 대기시간(초)",
+ "mod_epicloot_config_basecooldown": "기본 재사용 대기시간(초)",
+ "mod_epicloot_config_cooldownperrarity": "희귀도당 재사용 대기시간(초)",
+ "mod_epicloot_config_cooldownpervalue": "수치당 재사용 대기시간(초)",
+ "mod_epicloot_config_buffduration": "지속 시간(초)",
+ "mod_epicloot_config_tickinterval": "틱 간격(초)",
+ "mod_epicloot_config_radius": "반경(m)",
+ "mod_epicloot_config_baseradius": "기본 반경(m)",
+ "mod_epicloot_config_radiuspertier": "수치당 반경(m)",
+ "mod_epicloot_config_maxcharges": "충전까지 타격 수",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "충전까지 타격 수(기본)",
+ "mod_epicloot_config_chargespervalue": "수치당 절약 타격 수",
+ "mod_epicloot_config_mincharges": "최소 충전 타격 수",
+ "mod_epicloot_config_mincooldown": "최소 재사용 대기시간(초)",
+ "mod_epicloot_config_damagepertrigger": "발동당 피해",
+ "mod_epicloot_config_eitrpertrigger": "발동당 에이트르",
+ "mod_epicloot_config_damagemultiplier": "피해 배율",
+ "mod_epicloot_config_damagefraction": "피해 비율",
+ "mod_epicloot_config_damagepervalue": "수치당 피해",
+ "mod_epicloot_config_frostpertier": "수치당 냉기 피해",
+ "mod_epicloot_config_poisonpertier": "수치당 독 피해",
+ "mod_epicloot_config_spawnheight": "생성 높이(m)",
+ "mod_epicloot_config_spawnradius": "생성 반경(m)",
+ "mod_epicloot_config_spawninterval": "생성 간격(초)",
+ "mod_epicloot_config_mindistance": "최소 발사 거리(m)",
+ "mod_epicloot_config_maxdistance": "최대 발사 거리(m)",
+ "mod_epicloot_config_projectilespeed": "발사체 속도(m/초)",
+ "mod_epicloot_config_explosionradius": "폭발 반경(m)",
+ "mod_epicloot_config_conelength": "원뿔 길이(m)",
+ "mod_epicloot_config_conemaxwidth": "원뿔 너비(m)",
+ "mod_epicloot_config_dischargeignorewindow": "방전 무시 시간(초)",
+ "mod_epicloot_config_minmovespeed": "최소 이동 속도",
+ "mod_epicloot_config_patchradius": "장판 반경(m)",
+ "mod_epicloot_config_patchlifetime": "장판 지속 시간(초)",
+ "mod_epicloot_config_patchtickinterval": "장판 틱 간격(초)",
+ "mod_epicloot_config_speedfloor": "속도 하한",
+ "mod_epicloot_config_batlifetime": "박쥐 지속 시간(초)",
+ "mod_epicloot_config_summoncountoffset": "소환 수 보정",
+ "mod_epicloot_config_concurrentpersummon": "소환당 최대 동시 수",
+ "mod_epicloot_config_maxreduction": "최대 감소",
+ "mod_epicloot_config_maxhealthpercentpervalue": "수치당 최대 체력 %",
+ "mod_epicloot_config_minhealthreduction": "최소 체력 감소",
+ "mod_epicloot_config_carryweightpervalue": "수치당 소지 무게",
+ "mod_epicloot_config_minresultingcarryweight": "최소 소지 무게",
+ "mod_epicloot_config_weightthreshold": "무게 기준치",
+ "mod_epicloot_config_weightperbonus": "보너스당 무게",
+ "mod_epicloot_config_eitrcostperdamage": "피해당 에이트르 소모",
+ "mod_epicloot_config_minskilllevel": "최소 숙련도 레벨",
+ "mod_epicloot_config_levelweightexponent": "레벨 가중치 지수",
+ "mod_epicloot_config_maxlevelupsperproc": "발동당 최대 레벨업 횟수",
+ "mod_epicloot_config_maxmultiplier": "최대 전리품 배율",
+ "mod_epicloot_config_maxunitsperentry": "항목당 최대 개수",
+ "mod_epicloot_config_maxaddedunitsperdeath": "처치당 최대 추가 개수",
+ "mod_epicloot_config_rollbase": "굴림 기본값",
+ "mod_epicloot_config_rollpervalue": "수치당 굴림",
+ "mod_epicloot_config_rollcurve": "굴림 곡선",
+ "mod_epicloot_config_floorfraction": "하한 비율",
+ "mod_epicloot_config_chitin": "갑각소",
+ "mod_epicloot_config_copperscrap": "구리 고철",
+ "mod_epicloot_config_ironscrap": "고철",
+ "mod_epicloot_config_runestoneepic": "에픽 룬스톤",
+ "mod_epicloot_config_runestonelegendary": "전설 룬스톤",
"mod_epicloot_me_modifylowhealth_config_max": "낮은 체력 최대 기준치",
"mod_epicloot_me_riches_config_coins": "동전 가치",
"mod_epicloot_me_riches_config_amber": "호박 가치",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(영구)",
"mod_epicloot_legendary_brokkrgift": "전설급 브로크의 선물",
"mod_epicloot_mythic_brokkrgift": "신화급 브로크의 선물",
+ "mod_epicloot_ancient_brokkrgift": "고대급 브로크의 선물",
"mod_epicloot_slotchisel_introduce": "지극히 강력한 유물로, 어떤 마법 아이템에든 파편 슬롯을 새겨 넣는다고 전해진다.",
"mod_epicloot_slotchisel_hint": "마법 아이템 위로 끌어다 놓아 파편 슬롯을 추가합니다",
"mod_epicloot_slotchisel_confirm_title": "파편 슬롯을 추가할까요?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "회피 구르기마다 독과 화상 효과에서 {0}초를 제거합니다.",
"mod_epicloot_me_stormrider_display": "폭풍 속 이동 속도 +{0} %",
"mod_epicloot_me_stormrider_desc": "폭풍이 몰아치는 동안 이동 속도 +{0} %.",
- "mod_epicloot_me_shockingcharge_display": "충격 충전",
- "mod_epicloot_me_shockingcharge_desc": "전투에서 적중할 때마다 충전이 쌓입니다. 최대 충전 시 다음 일격이 전방으로 번개 충격파를 내보내 이전 적중 피해의 일부를 입힙니다.",
- "mod_epicloot_me_forestsaid_display": "숲의 도움",
- "mod_epicloot_me_forestsaid_desc": "피격 시 얽어매는 뿌리가 솟구쳐 근처의 적을 붙잡습니다.",
- "mod_epicloot_me_corpserot_display": "시체 부패",
- "mod_epicloot_me_corpserot_desc": "처치한 적이 독 구름으로 터져 근처의 적을 중독시킬 수 있습니다.",
- "mod_epicloot_me_icyretribution_display": "얼음의 응징",
- "mod_epicloot_me_icyretribution_desc": "피격 시 주변에 서리 폭발을 일으킵니다. 재사용 대기시간은 희귀도에 따라 달라집니다.",
+ "mod_epicloot_me_shockingcharge_display": "충격 충전: {0:0.#}회 적중마다",
+ "mod_epicloot_me_shockingcharge_desc": "전투에서 {0:0.#}회 적중할 때마다 다음 일격이 전방으로 번개 충격파를 내보내 그 적중 피해의 {1:0.#}%를 입힙니다. 희귀도가 높을수록 필요한 적중 횟수가 줄어듭니다.",
+ "mod_epicloot_me_forestsaid_display": "숲의 도움: {0:0.#}m, 재사용 대기시간 {1:0.#}초",
+ "mod_epicloot_me_forestsaid_desc": "피격 시 얽어매는 뿌리가 솟구쳐 {0:0.#}미터 이내의 적을 붙잡습니다. 희귀도가 높을수록 범위가 넓어지며 재사용 대기시간은 {1:0.#}초입니다.",
+ "mod_epicloot_me_corpserot_display": "시체 부패: {0:0.#} 독",
+ "mod_epicloot_me_corpserot_desc": "처치한 적이 독 구름으로 터져 근처의 적에게 {0:0.#}의 독 피해를 입힐 수 있습니다.",
+ "mod_epicloot_me_icyretribution_display": "얼음의 응징: {0:0.#} 냉기",
+ "mod_epicloot_me_icyretribution_desc": "피격 시 주변에 서리 폭발을 일으켜 {0:0.#}의 냉기 피해를 입힙니다. 희귀도가 높을수록 피해가 커지고 재사용 대기시간도 짧아집니다.",
"mod_epicloot_me_meteorsummoner_display": "운석 소환자: 적중 피해의 {0}배",
"mod_epicloot_me_meteorsummoner_desc": "무기로 {1}회 적중할 때마다 다음 일격이 대상에게 불타는 운석을 떨어뜨려, 그 일격 피해의 {0}배를 화염 피해로 입힙니다.",
- "mod_epicloot_me_everflow_display": "여왕의 영원한 흐름",
- "mod_epicloot_me_everflow_desc": "생물을 처치하면 체력·스태미나·에이트르 재생을 높이는 중첩 버프를 얻습니다.",
- "mod_epicloot_me_necroticfire_display": "괴사의 불꽃",
- "mod_epicloot_me_necroticfire_desc": "무기에서 물리 피해를 모두 제거하고, 그 전량을 화염 피해와 독 피해 양쪽으로 추가합니다.",
+ "mod_epicloot_me_everflow_display": "여왕의 영원한 흐름: 중첩당 재생 +{0:0.#}%",
+ "mod_epicloot_me_everflow_desc": "생물을 처치하면 체력·스태미나·에이트르 재생을 중첩당 {0:0.#}% 높이는 버프를 얻습니다(최대 {1:0.#}중첩).",
+ "mod_epicloot_me_necroticfire_display": "괴사의 불꽃: 화염·독 +{0:0.#}%",
+ "mod_epicloot_me_necroticfire_desc": "무기에서 물리 피해를 모두 제거하고, 그중 {0:0.#}%를 화염 피해로, 같은 양을 독 피해로 추가합니다.",
"mod_epicloot_circlet": "머리고리",
"mod_epicloot_belt": "벨트",
"mod_epicloot_remains": "유골",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "전설적인 {0}",
"mod_epicloot_basicmythicnameformat": "신화적인 {0}",
+ "mod_epicloot_basicancientnameformat": "고대의 {0}",
"mod_epicloot_rare_prefix1": "이상한",
"mod_epicloot_rare_prefix2": "희귀한",
"mod_epicloot_rare_prefix3": "수수께끼의",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "에픽",
"mod_epicloot_Legendary": "전설적인",
"mod_epicloot_Mythic": "신화적인",
+ "mod_epicloot_Ancient": "고대의",
"mod_epicloot_search_placeholder": "검색...",
"mod_epicloot_no_active_adventures": "활성화된 모험이 없습니다",
"mod_epicloot_set_bonuses": "세트 보너스",
diff --git a/EpicLoot/localizations/Latvian.json b/EpicLoot/localizations/Latvian.json
index 0f57ca600..8ba9303ca 100644
--- a/EpicLoot/localizations/Latvian.json
+++ b/EpicLoot/localizations/Latvian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Krams",
"mod_epicloot_config_serpentscale": "Jūras čūskas zvīņa",
"mod_epicloot_config_triplechance": "Trīskārša loma iespēja",
- "mod_epicloot_config_secondspercent": "Sekundes uz 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekundes uz 1 %",
"mod_epicloot_config_procchance": "Iedarbināšanas iespēja",
"mod_epicloot_config_bonusrollsmin": "Min. bonusa laupījuma metieni",
"mod_epicloot_config_bonusrollsmax": "Maks. bonusa laupījuma metieni",
+ "mod_epicloot_config_cooldown": "Atjaunošanās (s)",
+ "mod_epicloot_config_basecooldown": "Bāzes atjaunošanās (s)",
+ "mod_epicloot_config_cooldownperrarity": "Atjaunošanās uz retumu (s)",
+ "mod_epicloot_config_cooldownpervalue": "Atjaunošanās uz vērtību (s)",
+ "mod_epicloot_config_buffduration": "Ilgums (s)",
+ "mod_epicloot_config_tickinterval": "Tikšķa intervāls (s)",
+ "mod_epicloot_config_radius": "Rādiuss (m)",
+ "mod_epicloot_config_baseradius": "Bāzes rādiuss (m)",
+ "mod_epicloot_config_radiuspertier": "Rādiuss uz vērtību (m)",
+ "mod_epicloot_config_maxcharges": "Sitieni līdz uzlādei",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Sitieni līdz uzlādei (bāze)",
+ "mod_epicloot_config_chargespervalue": "Ietaupītie sitieni uz vērtību",
+ "mod_epicloot_config_mincharges": "Min. sitieni līdz uzlādei",
+ "mod_epicloot_config_mincooldown": "Min. atjaunošanās (s)",
+ "mod_epicloot_config_damagepertrigger": "Bojājums uz iedarbināšanu",
+ "mod_epicloot_config_eitrpertrigger": "Eitrs uz iedarbināšanu",
+ "mod_epicloot_config_damagemultiplier": "Bojājuma reizinātājs",
+ "mod_epicloot_config_damagefraction": "Bojājuma daļa",
+ "mod_epicloot_config_damagepervalue": "Bojājums uz vērtību",
+ "mod_epicloot_config_frostpertier": "Sala bojājums uz vērtību",
+ "mod_epicloot_config_poisonpertier": "Indes bojājums uz vērtību",
+ "mod_epicloot_config_spawnheight": "Parādīšanās augstums (m)",
+ "mod_epicloot_config_spawnradius": "Parādīšanās rādiuss (m)",
+ "mod_epicloot_config_spawninterval": "Parādīšanās intervāls (s)",
+ "mod_epicloot_config_mindistance": "Min. izmešanas attālums (m)",
+ "mod_epicloot_config_maxdistance": "Maks. izmešanas attālums (m)",
+ "mod_epicloot_config_projectilespeed": "Šāviņa ātrums (m/s)",
+ "mod_epicloot_config_explosionradius": "Sprādziena rādiuss (m)",
+ "mod_epicloot_config_conelength": "Konusa garums (m)",
+ "mod_epicloot_config_conemaxwidth": "Konusa platums (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Izlādes ignorēšanas logs (s)",
+ "mod_epicloot_config_minmovespeed": "Min. kustības ātrums",
+ "mod_epicloot_config_patchradius": "Laukuma rādiuss (m)",
+ "mod_epicloot_config_patchlifetime": "Laukuma ilgums (s)",
+ "mod_epicloot_config_patchtickinterval": "Laukuma tikšķa intervāls (s)",
+ "mod_epicloot_config_speedfloor": "Ātruma apakšējā robeža",
+ "mod_epicloot_config_batlifetime": "Sikspārņa ilgums (s)",
+ "mod_epicloot_config_summoncountoffset": "Izsaukto skaita nobīde",
+ "mod_epicloot_config_concurrentpersummon": "Maks. vienlaicīgi uz izsaukumu",
+ "mod_epicloot_config_maxreduction": "Maks. samazinājums",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% no maks. veselības uz vērtību",
+ "mod_epicloot_config_minhealthreduction": "Min. veselības samazinājums",
+ "mod_epicloot_config_carryweightpervalue": "Nēsājamais svars uz vērtību",
+ "mod_epicloot_config_minresultingcarryweight": "Min. nēsājamais svars",
+ "mod_epicloot_config_weightthreshold": "Svara slieksnis",
+ "mod_epicloot_config_weightperbonus": "Svars uz bonusu",
+ "mod_epicloot_config_eitrcostperdamage": "Eitra izmaksas uz bojājumu",
+ "mod_epicloot_config_minskilllevel": "Min. prasmes līmenis",
+ "mod_epicloot_config_levelweightexponent": "Līmeņa svara eksponents",
+ "mod_epicloot_config_maxlevelupsperproc": "Maks. līmeņa kāpumi uz iedarbināšanu",
+ "mod_epicloot_config_maxmultiplier": "Maks. laupījuma reizinātājs",
+ "mod_epicloot_config_maxunitsperentry": "Maks. vienību uz ierakstu",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Maks. pievienoto vienību uz nogalināšanu",
+ "mod_epicloot_config_rollbase": "Metiena bāze",
+ "mod_epicloot_config_rollpervalue": "Metiens uz vērtību",
+ "mod_epicloot_config_rollcurve": "Metiena līkne",
+ "mod_epicloot_config_floorfraction": "Apakšējās robežas daļa",
+ "mod_epicloot_config_chitin": "Hitīns",
+ "mod_epicloot_config_copperscrap": "Vara lūžņi",
+ "mod_epicloot_config_ironscrap": "Dzelzs lūžņi",
+ "mod_epicloot_config_runestoneepic": "Episks rūnakmens",
+ "mod_epicloot_config_runestonelegendary": "Leģendārs rūnakmens",
"mod_epicloot_me_modifylowhealth_config_max": "Maksimālais zemas veselības slieksnis",
"mod_epicloot_me_riches_config_coins": "Monētu vērtība",
"mod_epicloot_me_riches_config_amber": "Dzintara vērtība",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(pastāvīgi)",
"mod_epicloot_legendary_brokkrgift": "Leģendāra Brokra dāvana",
"mod_epicloot_mythic_brokkrgift": "Mītiska Brokra dāvana",
+ "mod_epicloot_ancient_brokkrgift": "Sena Brokra dāvana",
"mod_epicloot_slotchisel_introduce": "Ārkārtīgi varens artefakts, par kuru stāsta, ka tas jebkurā maģiskā priekšmetā iestrādā šķembu ligzdu.",
"mod_epicloot_slotchisel_hint": "Velc to uz maģiska priekšmeta, lai pievienotu šķembu ligzdu",
"mod_epicloot_slotchisel_confirm_title": "Pievienot šķembu ligzdu?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Katrs izvairīšanās veltnis noņem {0} sekundes no taviem indes un degšanas efektiem.",
"mod_epicloot_me_stormrider_display": "+{0} % kustības ātruma vētrās",
"mod_epicloot_me_stormrider_desc": "+{0} % kustības ātruma, kamēr plosās vētra.",
- "mod_epicloot_me_shockingcharge_display": "Trieciena lādiņš",
- "mod_epicloot_me_shockingcharge_desc": "Tavi triecieni kaujā krāj lādiņus. Pie pilna lādiņa nākamais trieciens atbrīvo uz priekšu vērstu zibens triecienviļni, kas nodara daļu no tavu iepriekšējo triecienu bojājuma.",
- "mod_epicloot_me_forestsaid_display": "Meža palīdzība",
- "mod_epicloot_me_forestsaid_desc": "Kad tevi trāpa, izlaužas savijošās saknes un imobilizē tuvumā esošos ienaidniekus.",
- "mod_epicloot_me_corpserot_display": "Līķu puve",
- "mod_epicloot_me_corpserot_desc": "Tavi nogalinātie ienaidnieki var uzsprāgt indes mākonī, saindējot tuvumā esošos pretiniekus.",
- "mod_epicloot_me_icyretribution_display": "Ledaina atmaksa",
- "mod_epicloot_me_icyretribution_desc": "Kad tevi trāpa, ap tevi uzsprāgst sala nova. Atjaunošanās laiks mērogojas ar retumu.",
+ "mod_epicloot_me_shockingcharge_display": "Trieciena lādiņš: ik pēc {0:0.#} triecieniem",
+ "mod_epicloot_me_shockingcharge_desc": "Ik pēc {0:0.#} triecieniem kaujā nākamais trieciens atbrīvo uz priekšu vērstu zibens triecienviļni, kas nodara {1:0.#}% no šo triecienu bojājuma. Augstāks retums prasa mazāk triecienu.",
+ "mod_epicloot_me_forestsaid_display": "Meža palīdzība: {0:0.#} m, {1:0.#} s atjaunošanās",
+ "mod_epicloot_me_forestsaid_desc": "Kad tevi trāpa, izlaužas savijošās saknes un imobilizē ienaidniekus {0:0.#} metru rādiusā. Augstāks retums sniedzas tālāk un atjaunojas {1:0.#} sekundēs.",
+ "mod_epicloot_me_corpserot_display": "Līķu puve: {0:0.#} indes",
+ "mod_epicloot_me_corpserot_desc": "Tavi nogalinātie ienaidnieki var uzsprāgt indes mākonī, nodarot tuvumā esošajiem pretiniekiem {0:0.#} indes bojājumu.",
+ "mod_epicloot_me_icyretribution_display": "Ledaina atmaksa: {0:0.#} sala",
+ "mod_epicloot_me_icyretribution_desc": "Kad tevi trāpa, ap tevi uzsprāgst sala nova, kas nodara {0:0.#} sala bojājumu. Augstāks retums sit spēcīgāk un atjaunojas ātrāk.",
"mod_epicloot_me_meteorsummoner_display": "Meteoru izsaucējs: {0}× trieciena bojājums",
"mod_epicloot_me_meteorsummoner_desc": "Ik pēc {1} ieroča triecieniem tavs nākamais trieciens nogāž uz mērķi liesmojošu meteoru, kas nodara {0}× šī trieciena bojājuma kā uguni.",
- "mod_epicloot_me_everflow_display": "Karalienes mūžīgā plūsma",
- "mod_epicloot_me_everflow_desc": "Radības nogalināšana piešķir krājošu uzlabojumu, kas veicina veselības, izturības un eitra atjaunošanos.",
- "mod_epicloot_me_necroticfire_display": "Nekrotiska uguns",
- "mod_epicloot_me_necroticfire_desc": "Noņem no tava ieroča visu fizisko bojājumu un pievieno šo pilno apjomu gan kā uguns, gan kā indes bojājumu.",
+ "mod_epicloot_me_everflow_display": "Karalienes mūžīgā plūsma: +{0:0.#}% atjaunošanās uz slāni",
+ "mod_epicloot_me_everflow_desc": "Radības nogalināšana piešķir krājošu uzlabojumu, kas par {0:0.#}% uz slāni veicina veselības, izturības un eitra atjaunošanos, līdz {1:0.#} slāņiem.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotiska uguns: +{0:0.#}% uguns un indes",
+ "mod_epicloot_me_necroticfire_desc": "Noņem no tava ieroča visu fizisko bojājumu un pievieno {0:0.#}% no tā kā uguns bojājumu un tikpat daudz kā indes bojājumu.",
"mod_epicloot_circlet": "aplauzums",
"mod_epicloot_belt": "josta",
"mod_epicloot_remains": "paliekas",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendārs {0}",
"mod_epicloot_basicmythicnameformat": "Mītiķis {0}",
+ "mod_epicloot_basicancientnameformat": "Sens {0}",
"mod_epicloot_rare_prefix1": "Brīnums",
"mod_epicloot_rare_prefix2": "Retais",
"mod_epicloot_rare_prefix3": "Noslēpumains",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Episki",
"mod_epicloot_Legendary": "Legendāri",
"mod_epicloot_Mythic": "Mītiski",
+ "mod_epicloot_Ancient": "Sens",
"mod_epicloot_search_placeholder": "Meklēt...",
"mod_epicloot_no_active_adventures": "Nav aktīvu piedzīvojumu",
"mod_epicloot_set_bonuses": "Komplekta bonusi",
diff --git a/EpicLoot/localizations/Lithuanian.json b/EpicLoot/localizations/Lithuanian.json
index 96f9e6ab7..a87816931 100644
--- a/EpicLoot/localizations/Lithuanian.json
+++ b/EpicLoot/localizations/Lithuanian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Titnagas",
"mod_epicloot_config_serpentscale": "Jūros gyvatės žvynas",
"mod_epicloot_config_triplechance": "Trigubo laimikio tikimybė",
- "mod_epicloot_config_secondspercent": "Sekundės vienam 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekundės vienam 1 %",
"mod_epicloot_config_procchance": "Suveikimo tikimybė",
"mod_epicloot_config_bonusrollsmin": "Min. papildomų grobio metimų",
"mod_epicloot_config_bonusrollsmax": "Maks. papildomų grobio metimų",
+ "mod_epicloot_config_cooldown": "Atvėsimas (s)",
+ "mod_epicloot_config_basecooldown": "Bazinis atvėsimas (s)",
+ "mod_epicloot_config_cooldownperrarity": "Atvėsimas pagal retumą (s)",
+ "mod_epicloot_config_cooldownpervalue": "Atvėsimas pagal vertę (s)",
+ "mod_epicloot_config_buffduration": "Trukmė (s)",
+ "mod_epicloot_config_tickinterval": "Takto intervalas (s)",
+ "mod_epicloot_config_radius": "Spindulys (m)",
+ "mod_epicloot_config_baseradius": "Bazinis spindulys (m)",
+ "mod_epicloot_config_radiuspertier": "Spindulys pagal vertę (m)",
+ "mod_epicloot_config_maxcharges": "Smūgių iki įkrovos",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Smūgių iki įkrovos (bazinis)",
+ "mod_epicloot_config_chargespervalue": "Sutaupyti smūgiai pagal vertę",
+ "mod_epicloot_config_mincharges": "Min. smūgių iki įkrovos",
+ "mod_epicloot_config_mincooldown": "Min. atvėsimas (s)",
+ "mod_epicloot_config_damagepertrigger": "Žala už suveikimą",
+ "mod_epicloot_config_eitrpertrigger": "Eitras už suveikimą",
+ "mod_epicloot_config_damagemultiplier": "Žalos daugiklis",
+ "mod_epicloot_config_damagefraction": "Žalos dalis",
+ "mod_epicloot_config_damagepervalue": "Žala pagal vertę",
+ "mod_epicloot_config_frostpertier": "Šalčio žala pagal vertę",
+ "mod_epicloot_config_poisonpertier": "Nuodų žala pagal vertę",
+ "mod_epicloot_config_spawnheight": "Atsiradimo aukštis (m)",
+ "mod_epicloot_config_spawnradius": "Atsiradimo spindulys (m)",
+ "mod_epicloot_config_spawninterval": "Atsiradimo intervalas (s)",
+ "mod_epicloot_config_mindistance": "Min. paleidimo atstumas (m)",
+ "mod_epicloot_config_maxdistance": "Maks. paleidimo atstumas (m)",
+ "mod_epicloot_config_projectilespeed": "Sviedinio greitis (m/s)",
+ "mod_epicloot_config_explosionradius": "Sprogimo spindulys (m)",
+ "mod_epicloot_config_conelength": "Kūgio ilgis (m)",
+ "mod_epicloot_config_conemaxwidth": "Kūgio plotis (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Iškrovos ignoravimo langas (s)",
+ "mod_epicloot_config_minmovespeed": "Min. judėjimo greitis",
+ "mod_epicloot_config_patchradius": "Zonos spindulys (m)",
+ "mod_epicloot_config_patchlifetime": "Zonos trukmė (s)",
+ "mod_epicloot_config_patchtickinterval": "Zonos takto intervalas (s)",
+ "mod_epicloot_config_speedfloor": "Greičio apatinė riba",
+ "mod_epicloot_config_batlifetime": "Šikšnosparnio trukmė (s)",
+ "mod_epicloot_config_summoncountoffset": "Prišauktų skaičiaus poslinkis",
+ "mod_epicloot_config_concurrentpersummon": "Maks. vienu metu vienam prišaukimui",
+ "mod_epicloot_config_maxreduction": "Maks. sumažinimas",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% maks. sveikatos pagal vertę",
+ "mod_epicloot_config_minhealthreduction": "Min. sveikatos sumažinimas",
+ "mod_epicloot_config_carryweightpervalue": "Nešamas svoris pagal vertę",
+ "mod_epicloot_config_minresultingcarryweight": "Min. nešamas svoris",
+ "mod_epicloot_config_weightthreshold": "Svorio riba",
+ "mod_epicloot_config_weightperbonus": "Svoris vienam priedui",
+ "mod_epicloot_config_eitrcostperdamage": "Eitro sąnaudos vienam žalos vienetui",
+ "mod_epicloot_config_minskilllevel": "Min. įgūdžio lygis",
+ "mod_epicloot_config_levelweightexponent": "Lygio svorio rodiklis",
+ "mod_epicloot_config_maxlevelupsperproc": "Maks. lygio pakilimų už suveikimą",
+ "mod_epicloot_config_maxmultiplier": "Maks. grobio daugiklis",
+ "mod_epicloot_config_maxunitsperentry": "Maks. vienetų vienam įrašui",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Maks. pridėtų vienetų už nužudymą",
+ "mod_epicloot_config_rollbase": "Metimo bazė",
+ "mod_epicloot_config_rollpervalue": "Metimas pagal vertę",
+ "mod_epicloot_config_rollcurve": "Metimo kreivė",
+ "mod_epicloot_config_floorfraction": "Apatinės ribos dalis",
+ "mod_epicloot_config_chitin": "Chitinas",
+ "mod_epicloot_config_copperscrap": "Vario laužas",
+ "mod_epicloot_config_ironscrap": "Geležies laužas",
+ "mod_epicloot_config_runestoneepic": "Epinis runų akmuo",
+ "mod_epicloot_config_runestonelegendary": "Legendinis runų akmuo",
"mod_epicloot_me_modifylowhealth_config_max": "Didžiausia žemos sveikatos riba",
"mod_epicloot_me_riches_config_coins": "Monetų vertė",
"mod_epicloot_me_riches_config_amber": "Gintaro vertė",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(visam laikui)",
"mod_epicloot_legendary_brokkrgift": "Legendinė Brokro dovana",
"mod_epicloot_mythic_brokkrgift": "Mitolinė Brokro dovana",
+ "mod_epicloot_ancient_brokkrgift": "Senovinė Brokro dovana",
"mod_epicloot_slotchisel_introduce": "Nepaprastai galingas artefaktas, apie kurį sakoma, kad jis į bet kurį magišką daiktą įtaiso skeveldrų lizdą.",
"mod_epicloot_slotchisel_hint": "Užvilkite ant magiško daikto, kad pridėtumėte skeveldrų lizdą",
"mod_epicloot_slotchisel_confirm_title": "Pridėti skeveldrų lizdą?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Kiekvienas išsisukimo kūlvirstis pašalina {0} sekundes nuo tavo nuodų ir degimo efektų.",
"mod_epicloot_me_stormrider_display": "+{0} % judėjimo greičio per audras",
"mod_epicloot_me_stormrider_desc": "+{0} % judėjimo greičio, kol siautėja audra.",
- "mod_epicloot_me_shockingcharge_display": "Trenkiantis krūvis",
- "mod_epicloot_me_shockingcharge_desc": "Tavo smūgiai kovoje kaupia krūvius. Esant pilnam krūviui kitas smūgis paleidžia į priekį nukreiptą žaibo smūginę bangą, kuri padaro dalį tavo ankstesnių smūgių žalos.",
- "mod_epicloot_me_forestsaid_display": "Miško pagalba",
- "mod_epicloot_me_forestsaid_desc": "Kai į tave pataiko, iš žemės prasiveržia pinančios šaknys ir sukausto netoliese esančius priešus.",
- "mod_epicloot_me_corpserot_display": "Lavonų puvinys",
- "mod_epicloot_me_corpserot_desc": "Tavo nužudyti priešai gali sprogti nuodų debesyje, nuodydami netoliese esančius priešininkus.",
- "mod_epicloot_me_icyretribution_display": "Ledinis atpildas",
- "mod_epicloot_me_icyretribution_desc": "Kai į tave pataiko, aplink tave sprogsta šalčio nova. Atsistatymo laikas keičiasi pagal retumą.",
+ "mod_epicloot_me_shockingcharge_display": "Trenkiantis krūvis: kas {0:0.#} smūgius",
+ "mod_epicloot_me_shockingcharge_desc": "Kas {0:0.#} smūgius kovoje kitas smūgis paleidžia į priekį nukreiptą žaibo smūginę bangą, kuri padaro {1:0.#}% tų smūgių žalos. Didesnis retumas reikalauja mažiau smūgių.",
+ "mod_epicloot_me_forestsaid_display": "Miško pagalba: {0:0.#} m, {1:0.#} s atvėsimas",
+ "mod_epicloot_me_forestsaid_desc": "Kai į tave pataiko, iš žemės prasiveržia pinančios šaknys ir sukausto priešus {0:0.#} metrų spinduliu. Didesnis retumas siekia toliau ir atsistato per {1:0.#} sekundes.",
+ "mod_epicloot_me_corpserot_display": "Lavonų puvinys: {0:0.#} nuodų",
+ "mod_epicloot_me_corpserot_desc": "Tavo nužudyti priešai gali sprogti nuodų debesyje, padarydami netoliese esantiems priešininkams {0:0.#} nuodų žalos.",
+ "mod_epicloot_me_icyretribution_display": "Ledinis atpildas: {0:0.#} šalčio",
+ "mod_epicloot_me_icyretribution_desc": "Kai į tave pataiko, aplink tave sprogsta šalčio nova, padaranti {0:0.#} šalčio žalos. Didesnis retumas smogia stipriau ir atsistato greičiau.",
"mod_epicloot_me_meteorsummoner_display": "Meteorų šaukėjas: {0}× smūgio žala",
"mod_epicloot_me_meteorsummoner_desc": "Kas {1} ginklo smūgius tavo kitas smūgis nuleidžia ant taikinio liepsnojantį meteorą, kuris padaro {0}× to smūgio žalos kaip ugnį.",
- "mod_epicloot_me_everflow_display": "Karalienės amžinas srautas",
- "mod_epicloot_me_everflow_desc": "Nužudžius padarą suteikiamas besikaupiantis priedas, kuris didina tavo sveikatos, ištvermės ir eitro atsinaujinimą.",
- "mod_epicloot_me_necroticfire_display": "Nekrozinė ugnis",
- "mod_epicloot_me_necroticfire_desc": "Pašalina iš tavo ginklo visą fizinę žalą ir prideda visą tą kiekį ir kaip ugnies, ir kaip nuodų žalą.",
+ "mod_epicloot_me_everflow_display": "Karalienės amžinas srautas: +{0:0.#}% atsinaujinimo už sluoksnį",
+ "mod_epicloot_me_everflow_desc": "Nužudžius padarą suteikiamas besikaupiantis priedas, kuris už kiekvieną sluoksnį {0:0.#}% didina tavo sveikatos, ištvermės ir eitro atsinaujinimą, iki {1:0.#} sluoksnių.",
+ "mod_epicloot_me_necroticfire_display": "Nekrozinė ugnis: +{0:0.#}% ugnies ir nuodų",
+ "mod_epicloot_me_necroticfire_desc": "Pašalina iš tavo ginklo visą fizinę žalą ir prideda {0:0.#}% jos kaip ugnies žalą bei tiek pat kaip nuodų žalą.",
"mod_epicloot_circlet": "apsiaustas",
"mod_epicloot_belt": "diržas",
"mod_epicloot_remains": "likučiai",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendinis {0}",
"mod_epicloot_basicmythicnameformat": "Mitinis {0}",
+ "mod_epicloot_basicancientnameformat": "Senovinis {0}",
"mod_epicloot_rare_prefix1": "Keistas",
"mod_epicloot_rare_prefix2": "Retas",
"mod_epicloot_rare_prefix3": "Slaptas",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epas",
"mod_epicloot_Legendary": "Legendinis",
"mod_epicloot_Mythic": "Mitolinis",
+ "mod_epicloot_Ancient": "Senovinis",
"mod_epicloot_search_placeholder": "Ieškoti...",
"mod_epicloot_no_active_adventures": "Nėra aktyvių nuotykių",
"mod_epicloot_set_bonuses": "Rinkinys premijos",
diff --git a/EpicLoot/localizations/Macedonian.json b/EpicLoot/localizations/Macedonian.json
index f48755ec1..ea78a0091 100644
--- a/EpicLoot/localizations/Macedonian.json
+++ b/EpicLoot/localizations/Macedonian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Кремен",
"mod_epicloot_config_serpentscale": "Лушпа од морска змија",
"mod_epicloot_config_triplechance": "Шанса за троен улов",
- "mod_epicloot_config_secondspercent": "Секунди на 1 %",
+ "mod_epicloot_config_secondsperpercent": "Секунди на 1 %",
"mod_epicloot_config_procchance": "Шанса за активирање",
"mod_epicloot_config_bonusrollsmin": "Мин. бонус фрлања за плен",
"mod_epicloot_config_bonusrollsmax": "Макс. бонус фрлања за плен",
+ "mod_epicloot_config_cooldown": "Одмор (с)",
+ "mod_epicloot_config_basecooldown": "Основен одмор (с)",
+ "mod_epicloot_config_cooldownperrarity": "Одмор по реткост (с)",
+ "mod_epicloot_config_cooldownpervalue": "Одмор по вредност (с)",
+ "mod_epicloot_config_buffduration": "Траење (с)",
+ "mod_epicloot_config_tickinterval": "Интервал на чекор (с)",
+ "mod_epicloot_config_radius": "Радиус (м)",
+ "mod_epicloot_config_baseradius": "Основен радиус (м)",
+ "mod_epicloot_config_radiuspertier": "Радиус по вредност (м)",
+ "mod_epicloot_config_maxcharges": "Удари до полнење",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Удари до полнење (основно)",
+ "mod_epicloot_config_chargespervalue": "Заштедени удари по вредност",
+ "mod_epicloot_config_mincharges": "Мин. удари до полнење",
+ "mod_epicloot_config_mincooldown": "Мин. одмор (с)",
+ "mod_epicloot_config_damagepertrigger": "Штета по активирање",
+ "mod_epicloot_config_eitrpertrigger": "Еитр по активирање",
+ "mod_epicloot_config_damagemultiplier": "Множител на штета",
+ "mod_epicloot_config_damagefraction": "Дел од штетата",
+ "mod_epicloot_config_damagepervalue": "Штета по вредност",
+ "mod_epicloot_config_frostpertier": "Мразна штета по вредност",
+ "mod_epicloot_config_poisonpertier": "Отровна штета по вредност",
+ "mod_epicloot_config_spawnheight": "Висина на појава (м)",
+ "mod_epicloot_config_spawnradius": "Радиус на појава (м)",
+ "mod_epicloot_config_spawninterval": "Интервал на појава (с)",
+ "mod_epicloot_config_mindistance": "Мин. растојание на исфрлање (м)",
+ "mod_epicloot_config_maxdistance": "Макс. растојание на исфрлање (м)",
+ "mod_epicloot_config_projectilespeed": "Брзина на проектил (м/с)",
+ "mod_epicloot_config_explosionradius": "Радиус на експлозија (м)",
+ "mod_epicloot_config_conelength": "Должина на конусот (м)",
+ "mod_epicloot_config_conemaxwidth": "Ширина на конусот (м)",
+ "mod_epicloot_config_dischargeignorewindow": "Прозорец за игнорирање на празнење (с)",
+ "mod_epicloot_config_minmovespeed": "Мин. брзина на движење",
+ "mod_epicloot_config_patchradius": "Радиус на дамката (м)",
+ "mod_epicloot_config_patchlifetime": "Траење на дамката (с)",
+ "mod_epicloot_config_patchtickinterval": "Интервал на чекор на дамката (с)",
+ "mod_epicloot_config_speedfloor": "Долна граница на брзината",
+ "mod_epicloot_config_batlifetime": "Траење на лилјакот (с)",
+ "mod_epicloot_config_summoncountoffset": "Поместување на бројот повикани",
+ "mod_epicloot_config_concurrentpersummon": "Макс. истовремени по повик",
+ "mod_epicloot_config_maxreduction": "Макс. намалување",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% од макс. здравје по вредност",
+ "mod_epicloot_config_minhealthreduction": "Мин. намалување на здравјето",
+ "mod_epicloot_config_carryweightpervalue": "Носивост по вредност",
+ "mod_epicloot_config_minresultingcarryweight": "Мин. носивост",
+ "mod_epicloot_config_weightthreshold": "Праг на тежина",
+ "mod_epicloot_config_weightperbonus": "Тежина по бонус",
+ "mod_epicloot_config_eitrcostperdamage": "Трошок на еитр по штета",
+ "mod_epicloot_config_minskilllevel": "Мин. ниво на вештина",
+ "mod_epicloot_config_levelweightexponent": "Експонент на тежината на нивото",
+ "mod_epicloot_config_maxlevelupsperproc": "Макс. подигања на ниво по активирање",
+ "mod_epicloot_config_maxmultiplier": "Макс. множител на плен",
+ "mod_epicloot_config_maxunitsperentry": "Макс. единици по запис",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Макс. додадени единици по убиство",
+ "mod_epicloot_config_rollbase": "Основа на фрлањето",
+ "mod_epicloot_config_rollpervalue": "Фрлање по вредност",
+ "mod_epicloot_config_rollcurve": "Крива на фрлањето",
+ "mod_epicloot_config_floorfraction": "Дел на долната граница",
+ "mod_epicloot_config_chitin": "Хитин",
+ "mod_epicloot_config_copperscrap": "Бакарен отпад",
+ "mod_epicloot_config_ironscrap": "Железен отпад",
+ "mod_epicloot_config_runestoneepic": "Епски рунски камен",
+ "mod_epicloot_config_runestonelegendary": "Легендарен рунски камен",
"mod_epicloot_me_modifylowhealth_config_max": "Максимален праг на ниско здравје",
"mod_epicloot_me_riches_config_coins": "Вредност на монетите",
"mod_epicloot_me_riches_config_amber": "Вредност на килибарот",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(трајно)",
"mod_epicloot_legendary_brokkrgift": "Легендарен дар на Брок",
"mod_epicloot_mythic_brokkrgift": "Митски дар на Брок",
+ "mod_epicloot_ancient_brokkrgift": "Древен дар на Брок",
"mod_epicloot_slotchisel_introduce": "Исклучително моќен артефакт за кој се вели дека вградува лежиште за скршотини во кој било магичен предмет.",
"mod_epicloot_slotchisel_hint": "Повлечете го врз магичен предмет за да додадете лежиште за скршотини",
"mod_epicloot_slotchisel_confirm_title": "Да се додаде лежиште за скршотини?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Секое тркалање при одбегнување отстранува {0} секунди од твоите отровни и горечки ефекти.",
"mod_epicloot_me_stormrider_display": "+{0} % брзина на движење при бури",
"mod_epicloot_me_stormrider_desc": "+{0} % брзина на движење додека беснее бура.",
- "mod_epicloot_me_shockingcharge_display": "Шокантен полнеж",
- "mod_epicloot_me_shockingcharge_desc": "Твоите удари во борба собираат полнежи. При полн полнеж, следниот удар ослободува ударен бран од молњи нанапред што нанесува дел од штетата на твоите претходни удари.",
- "mod_epicloot_me_forestsaid_display": "Помош од шумата",
- "mod_epicloot_me_forestsaid_desc": "Кога ќе те погодат, избиваат оплеткувачки корени и ги обездвижуваат непријателите во близина.",
- "mod_epicloot_me_corpserot_display": "Мршојадна гнилеж",
- "mod_epicloot_me_corpserot_desc": "Непријателите што ги убиваш може да прснат во облак од отров, трујќи ги противниците во близина.",
- "mod_epicloot_me_icyretribution_display": "Ледена одмазда",
- "mod_epicloot_me_icyretribution_desc": "Кога ќе те погодат, детонираш ледена нова околу себе. Полнењето се скалира со реткоста.",
+ "mod_epicloot_me_shockingcharge_display": "Шокантен полнеж: на секои {0:0.#} удари",
+ "mod_epicloot_me_shockingcharge_desc": "На секои {0:0.#} удари во борба, следниот удар ослободува ударен бран од молњи нанапред што нанесува {1:0.#}% од штетата на тие удари. Повисока реткост бара помалку удари.",
+ "mod_epicloot_me_forestsaid_display": "Помош од шумата: {0:0.#} м, {1:0.#} с одмор",
+ "mod_epicloot_me_forestsaid_desc": "Кога ќе те погодат, избиваат оплеткувачки корени и ги обездвижуваат непријателите во радиус од {0:0.#} метри. Повисока реткост достига подалеку и се полни за {1:0.#} секунди.",
+ "mod_epicloot_me_corpserot_display": "Мршојадна гнилеж: {0:0.#} отров",
+ "mod_epicloot_me_corpserot_desc": "Непријателите што ги убиваш може да прснат во облак од отров, нанесувајќи {0:0.#} штета од отров на противниците во близина.",
+ "mod_epicloot_me_icyretribution_display": "Ледена одмазда: {0:0.#} мраз",
+ "mod_epicloot_me_icyretribution_desc": "Кога ќе те погодат, детонираш ледена нова околу себе што нанесува {0:0.#} штета од мраз. Повисока реткост удира посилно и се полни побрзо.",
"mod_epicloot_me_meteorsummoner_display": "Повикувач на метеори: {0}× штетата од ударот",
"mod_epicloot_me_meteorsummoner_desc": "На секои {1} удари со оружје, твојот следен удар повикува огнен метеор врз целта што нанесува {0}× од штетата на тој удар како оган.",
- "mod_epicloot_me_everflow_display": "Вечниот тек на Кралицата",
- "mod_epicloot_me_everflow_desc": "Убивањето суштество дава натрупувачки бонус што го засилува обновувањето на здравје, издржливост и еитр.",
- "mod_epicloot_me_necroticfire_display": "Некротичен оган",
- "mod_epicloot_me_necroticfire_desc": "Ја отстранува сета физичка штета од твоето оружје и го додава тој целосен износ и како огнена и како отровна штета.",
+ "mod_epicloot_me_everflow_display": "Вечниот тек на Кралицата: +{0:0.#}% обновување по слој",
+ "mod_epicloot_me_everflow_desc": "Убивањето суштество дава натрупувачки бонус што го засилува обновувањето на здравје, издржливост и еитр за {0:0.#}% по слој, до {1:0.#} слоеви.",
+ "mod_epicloot_me_necroticfire_display": "Некротичен оган: +{0:0.#}% оган и отров",
+ "mod_epicloot_me_necroticfire_desc": "Ја отстранува сета физичка штета од твоето оружје и додава {0:0.#}% од неа како огнена штета и исто толку како отровна.",
"mod_epicloot_circlet": "обод",
"mod_epicloot_belt": "колан",
"mod_epicloot_remains": "останоци",
@@ -1021,7 +1084,7 @@
"mod_epicloot_bounties_suffix_svalroot": "Svalroot",
"mod_epicloot_bounties_suffix_swiftspite": "Swiftspite",
"mod_epicloot_bounties_suffix_the_abandoned": "the Abandoned",
- "mod_epicloot_bounties_suffix_the_ancient": "the Ancient",
+ "mod_epicloot_bounties_suffix_the_ancient": "Древниот",
"mod_epicloot_bounties_suffix_the_betrayer": "the Betrayer",
"mod_epicloot_bounties_suffix_the_brutal": "the Brutal",
"mod_epicloot_bounties_suffix_the_brute": "the Brute",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Легендарен {0}",
"mod_epicloot_basicmythicnameformat": "Митичен {0}",
+ "mod_epicloot_basicancientnameformat": "Древен {0}",
"mod_epicloot_rare_prefix1": "Стран",
"mod_epicloot_rare_prefix2": "Ретка",
"mod_epicloot_rare_prefix3": "Криптична",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Епски",
"mod_epicloot_Legendary": "Легендарно",
"mod_epicloot_Mythic": "Mitski",
+ "mod_epicloot_Ancient": "Древен",
"mod_epicloot_search_placeholder": "Пребарај...",
"mod_epicloot_no_active_adventures": "Нема активни авантури",
"mod_epicloot_set_bonuses": "Бонуси од сет",
diff --git a/EpicLoot/localizations/Norwegian.json b/EpicLoot/localizations/Norwegian.json
index 2cd2fdf71..b90576088 100644
--- a/EpicLoot/localizations/Norwegian.json
+++ b/EpicLoot/localizations/Norwegian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Flint",
"mod_epicloot_config_serpentscale": "Sjøormskjell",
"mod_epicloot_config_triplechance": "Sjanse for trippelfangst",
- "mod_epicloot_config_secondspercent": "Sekunder per 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekunder per 1 %",
"mod_epicloot_config_procchance": "Utløsningssjanse",
"mod_epicloot_config_bonusrollsmin": "Min. bonusslag for bytte",
"mod_epicloot_config_bonusrollsmax": "Maks. bonusslag for bytte",
+ "mod_epicloot_config_cooldown": "Nedkjøling (s)",
+ "mod_epicloot_config_basecooldown": "Basisnedkjøling (s)",
+ "mod_epicloot_config_cooldownperrarity": "Nedkjøling per sjeldenhet (s)",
+ "mod_epicloot_config_cooldownpervalue": "Nedkjøling per verdi (s)",
+ "mod_epicloot_config_buffduration": "Varighet (s)",
+ "mod_epicloot_config_tickinterval": "Tikkintervall (s)",
+ "mod_epicloot_config_radius": "Radius (m)",
+ "mod_epicloot_config_baseradius": "Basisradius (m)",
+ "mod_epicloot_config_radiuspertier": "Radius per verdi (m)",
+ "mod_epicloot_config_maxcharges": "Treff til opplading",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Treff til opplading (basis)",
+ "mod_epicloot_config_chargespervalue": "Sparte treff per verdi",
+ "mod_epicloot_config_mincharges": "Min. treff til opplading",
+ "mod_epicloot_config_mincooldown": "Min. nedkjøling (s)",
+ "mod_epicloot_config_damagepertrigger": "Skade per utløsning",
+ "mod_epicloot_config_eitrpertrigger": "Eitr per utløsning",
+ "mod_epicloot_config_damagemultiplier": "Skademultiplikator",
+ "mod_epicloot_config_damagefraction": "Skadeandel",
+ "mod_epicloot_config_damagepervalue": "Skade per verdi",
+ "mod_epicloot_config_frostpertier": "Frostskade per verdi",
+ "mod_epicloot_config_poisonpertier": "Giftskade per verdi",
+ "mod_epicloot_config_spawnheight": "Spawnhøyde (m)",
+ "mod_epicloot_config_spawnradius": "Spawnradius (m)",
+ "mod_epicloot_config_spawninterval": "Spawnintervall (s)",
+ "mod_epicloot_config_mindistance": "Min. utskytningsavstand (m)",
+ "mod_epicloot_config_maxdistance": "Maks. utskytningsavstand (m)",
+ "mod_epicloot_config_projectilespeed": "Prosjektilfart (m/s)",
+ "mod_epicloot_config_explosionradius": "Eksplosjonsradius (m)",
+ "mod_epicloot_config_conelength": "Keglelengde (m)",
+ "mod_epicloot_config_conemaxwidth": "Keglebredde (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Ignoreringsvindu for utladning (s)",
+ "mod_epicloot_config_minmovespeed": "Min. bevegelsesfart",
+ "mod_epicloot_config_patchradius": "Feltradius (m)",
+ "mod_epicloot_config_patchlifetime": "Feltlevetid (s)",
+ "mod_epicloot_config_patchtickinterval": "Felttikkintervall (s)",
+ "mod_epicloot_config_speedfloor": "Fartsgulv",
+ "mod_epicloot_config_batlifetime": "Flaggermusens levetid (s)",
+ "mod_epicloot_config_summoncountoffset": "Forskyvning av tilkalt antall",
+ "mod_epicloot_config_concurrentpersummon": "Maks. samtidige per tilkalling",
+ "mod_epicloot_config_maxreduction": "Maks. reduksjon",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% av maks. helse per verdi",
+ "mod_epicloot_config_minhealthreduction": "Min. helsereduksjon",
+ "mod_epicloot_config_carryweightpervalue": "Bærevekt per verdi",
+ "mod_epicloot_config_minresultingcarryweight": "Min. bærevekt",
+ "mod_epicloot_config_weightthreshold": "Vektterskel",
+ "mod_epicloot_config_weightperbonus": "Vekt per bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-kostnad per skade",
+ "mod_epicloot_config_minskilllevel": "Min. ferdighetsnivå",
+ "mod_epicloot_config_levelweightexponent": "Eksponent for nivåvekting",
+ "mod_epicloot_config_maxlevelupsperproc": "Maks. nivåstigninger per utløsning",
+ "mod_epicloot_config_maxmultiplier": "Maks. byttemultiplikator",
+ "mod_epicloot_config_maxunitsperentry": "Maks. enheter per oppføring",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Maks. tilførte enheter per drap",
+ "mod_epicloot_config_rollbase": "Slagbasis",
+ "mod_epicloot_config_rollpervalue": "Slag per verdi",
+ "mod_epicloot_config_rollcurve": "Slagkurve",
+ "mod_epicloot_config_floorfraction": "Gulvandel",
+ "mod_epicloot_config_chitin": "Kitin",
+ "mod_epicloot_config_copperscrap": "Kopperskrap",
+ "mod_epicloot_config_ironscrap": "Skrapjern",
+ "mod_epicloot_config_runestoneepic": "Episk runestein",
+ "mod_epicloot_config_runestonelegendary": "Legendarisk runestein",
"mod_epicloot_me_modifylowhealth_config_max": "Maksterskel for lav helse",
"mod_epicloot_me_riches_config_coins": "Myntverdi",
"mod_epicloot_me_riches_config_amber": "Ravverdi",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanent)",
"mod_epicloot_legendary_brokkrgift": "Legendarisk Brokkrs gave",
"mod_epicloot_mythic_brokkrgift": "Mytisk Brokkrs gave",
+ "mod_epicloot_ancient_brokkrgift": "Eldgammel Brokkrs gave",
"mod_epicloot_slotchisel_introduce": "En overmåte mektig artefakt som skal kunne felle inn et skårspor i enhver magisk gjenstand.",
"mod_epicloot_slotchisel_hint": "Dra den på en magisk gjenstand for å legge til et skårspor",
"mod_epicloot_slotchisel_confirm_title": "Legge til skårspor?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Hver unnvikelsesrull fjerner {0} sekunder fra gift- og brenneffektene dine.",
"mod_epicloot_me_stormrider_display": "+{0} % bevegelsesfart i stormer",
"mod_epicloot_me_stormrider_desc": "+{0} % bevegelsesfart mens en storm raser.",
- "mod_epicloot_me_shockingcharge_display": "Sjokkerende ladning",
- "mod_epicloot_me_shockingcharge_desc": "Treffene dine i kamp bygger opp ladninger. Ved full ladning slipper neste treff løs en fremoverrettet lynsjokkbølge som gjør en del av skaden fra de forrige treffene dine.",
- "mod_epicloot_me_forestsaid_display": "Skogens hjelp",
- "mod_epicloot_me_forestsaid_desc": "Når du blir truffet, bryter fangende røtter frem og binder fiender i nærheten.",
- "mod_epicloot_me_corpserot_display": "Likråte",
- "mod_epicloot_me_corpserot_desc": "Fiender du dreper kan sprekke i en sky av gift og forgifte motstandere i nærheten.",
- "mod_epicloot_me_icyretribution_display": "Iskald gjengjeldelse",
- "mod_epicloot_me_icyretribution_desc": "Når du blir truffet, utløser du en frostnova rundt deg. Nedkjølingen skalerer med sjeldenheten.",
+ "mod_epicloot_me_shockingcharge_display": "Sjokkerende ladning: hvert {0:0.#} treff",
+ "mod_epicloot_me_shockingcharge_desc": "For hvert {0:0.#} treff i kamp slipper neste treff løs en fremoverrettet lynsjokkbølge som gjør {1:0.#}% av skaden fra de treffene. Høyere sjeldenhet krever færre treff.",
+ "mod_epicloot_me_forestsaid_display": "Skogens hjelp: {0:0.#} m, {1:0.#} s nedkjøling",
+ "mod_epicloot_me_forestsaid_desc": "Når du blir truffet, bryter fangende røtter frem og binder fiender innenfor {0:0.#} meter. Høyere sjeldenhet når lenger og lades opp på {1:0.#} sekunder.",
+ "mod_epicloot_me_corpserot_display": "Likråte: {0:0.#} gift",
+ "mod_epicloot_me_corpserot_desc": "Fiender du dreper kan sprekke i en sky av gift og gi motstandere i nærheten {0:0.#} giftskade.",
+ "mod_epicloot_me_icyretribution_display": "Iskald gjengjeldelse: {0:0.#} frost",
+ "mod_epicloot_me_icyretribution_desc": "Når du blir truffet, utløser du en frostnova rundt deg som gjør {0:0.#} frostskade. Høyere sjeldenhet slår hardere og lades raskere opp.",
"mod_epicloot_me_meteorsummoner_display": "Meteortilkaller: {0}× treffskade",
"mod_epicloot_me_meteorsummoner_desc": "Hvert {1} våpentreff kaller neste treff ned en brennende meteor på målet som gjør {0}× det treffets skade som ild.",
- "mod_epicloot_me_everflow_display": "Dronningens evige strøm",
- "mod_epicloot_me_everflow_desc": "Å drepe en skapning gir en stablbar effekt som øker helse-, utholdenhets- og eitrregenereringen din.",
- "mod_epicloot_me_necroticfire_display": "Nekrotisk ild",
- "mod_epicloot_me_necroticfire_desc": "Fjerner all fysisk skade fra våpenet ditt og legger til hele beløpet som både ild- og giftskade.",
+ "mod_epicloot_me_everflow_display": "Dronningens evige strøm: +{0:0.#}% regenerering per stabel",
+ "mod_epicloot_me_everflow_desc": "Å drepe en skapning gir en stablbar effekt som øker helse-, utholdenhets- og eitrregenereringen din med {0:0.#}% per stabel, opptil {1:0.#} stabler.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotisk ild: +{0:0.#}% ild og gift",
+ "mod_epicloot_me_necroticfire_desc": "Fjerner all fysisk skade fra våpenet ditt og legger til {0:0.#}% av den som ildskade og like mye som giftskade.",
"mod_epicloot_circlet": "krans",
"mod_epicloot_belt": "belte",
"mod_epicloot_remains": "rester",
@@ -1021,7 +1084,7 @@
"mod_epicloot_bounties_suffix_svalroot": "Svalroot",
"mod_epicloot_bounties_suffix_swiftspite": "Swiftspite",
"mod_epicloot_bounties_suffix_the_abandoned": "the Abandoned",
- "mod_epicloot_bounties_suffix_the_ancient": "the Ancient",
+ "mod_epicloot_bounties_suffix_the_ancient": "den eldgamle",
"mod_epicloot_bounties_suffix_the_betrayer": "the Betrayer",
"mod_epicloot_bounties_suffix_the_brutal": "the Brutal",
"mod_epicloot_bounties_suffix_the_brute": "the Brute",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendarisk {0}",
"mod_epicloot_basicmythicnameformat": "Mytisk {0}",
+ "mod_epicloot_basicancientnameformat": "Eldgammel {0}",
"mod_epicloot_rare_prefix1": "Merkelig",
"mod_epicloot_rare_prefix2": "Sjelden",
"mod_epicloot_rare_prefix3": "Kryptisk",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Episk",
"mod_epicloot_Legendary": "Legendarisk",
"mod_epicloot_Mythic": "Mytisk",
+ "mod_epicloot_Ancient": "Eldgammel",
"mod_epicloot_search_placeholder": "Søk...",
"mod_epicloot_no_active_adventures": "Ingen aktive eventyr",
"mod_epicloot_set_bonuses": "Settbonuser",
diff --git a/EpicLoot/localizations/Polish.json b/EpicLoot/localizations/Polish.json
index 90e4ce6bf..289fe151a 100644
--- a/EpicLoot/localizations/Polish.json
+++ b/EpicLoot/localizations/Polish.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Krzemień",
"mod_epicloot_config_serpentscale": "Łuska węża morskiego",
"mod_epicloot_config_triplechance": "Szansa na potrójny połów",
- "mod_epicloot_config_secondspercent": "Sekundy na 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekundy na 1 %",
"mod_epicloot_config_procchance": "Szansa aktywacji",
"mod_epicloot_config_bonusrollsmin": "Min. dodatkowych losowań łupu",
"mod_epicloot_config_bonusrollsmax": "Maks. dodatkowych losowań łupu",
+ "mod_epicloot_config_cooldown": "Odnowienie (s)",
+ "mod_epicloot_config_basecooldown": "Bazowe odnowienie (s)",
+ "mod_epicloot_config_cooldownperrarity": "Odnowienie na rzadkość (s)",
+ "mod_epicloot_config_cooldownpervalue": "Odnowienie na wartość (s)",
+ "mod_epicloot_config_buffduration": "Czas trwania (s)",
+ "mod_epicloot_config_tickinterval": "Interwał taktu (s)",
+ "mod_epicloot_config_radius": "Promień (m)",
+ "mod_epicloot_config_baseradius": "Bazowy promień (m)",
+ "mod_epicloot_config_radiuspertier": "Promień na wartość (m)",
+ "mod_epicloot_config_maxcharges": "Trafienia do naładowania",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Trafienia do naładowania (bazowo)",
+ "mod_epicloot_config_chargespervalue": "Zaoszczędzone trafienia na wartość",
+ "mod_epicloot_config_mincharges": "Min. trafień do naładowania",
+ "mod_epicloot_config_mincooldown": "Min. odnowienie (s)",
+ "mod_epicloot_config_damagepertrigger": "Obrażenia na aktywację",
+ "mod_epicloot_config_eitrpertrigger": "Eitr na aktywację",
+ "mod_epicloot_config_damagemultiplier": "Mnożnik obrażeń",
+ "mod_epicloot_config_damagefraction": "Ułamek obrażeń",
+ "mod_epicloot_config_damagepervalue": "Obrażenia na wartość",
+ "mod_epicloot_config_frostpertier": "Obrażenia od mrozu na wartość",
+ "mod_epicloot_config_poisonpertier": "Obrażenia od trucizny na wartość",
+ "mod_epicloot_config_spawnheight": "Wysokość pojawienia (m)",
+ "mod_epicloot_config_spawnradius": "Promień pojawienia (m)",
+ "mod_epicloot_config_spawninterval": "Interwał pojawiania (s)",
+ "mod_epicloot_config_mindistance": "Min. dystans wyrzutu (m)",
+ "mod_epicloot_config_maxdistance": "Maks. dystans wyrzutu (m)",
+ "mod_epicloot_config_projectilespeed": "Prędkość pocisku (m/s)",
+ "mod_epicloot_config_explosionradius": "Promień eksplozji (m)",
+ "mod_epicloot_config_conelength": "Długość stożka (m)",
+ "mod_epicloot_config_conemaxwidth": "Szerokość stożka (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Okno ignorowania wyładowania (s)",
+ "mod_epicloot_config_minmovespeed": "Min. prędkość ruchu",
+ "mod_epicloot_config_patchradius": "Promień strefy (m)",
+ "mod_epicloot_config_patchlifetime": "Czas trwania strefy (s)",
+ "mod_epicloot_config_patchtickinterval": "Interwał taktu strefy (s)",
+ "mod_epicloot_config_speedfloor": "Dolna granica prędkości",
+ "mod_epicloot_config_batlifetime": "Czas trwania nietoperza (s)",
+ "mod_epicloot_config_summoncountoffset": "Przesunięcie liczby przyzwań",
+ "mod_epicloot_config_concurrentpersummon": "Maks. jednoczesnych na przyzwanie",
+ "mod_epicloot_config_maxreduction": "Maks. redukcja",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% maks. zdrowia na wartość",
+ "mod_epicloot_config_minhealthreduction": "Min. redukcja zdrowia",
+ "mod_epicloot_config_carryweightpervalue": "Udźwig na wartość",
+ "mod_epicloot_config_minresultingcarryweight": "Min. udźwig",
+ "mod_epicloot_config_weightthreshold": "Próg wagi",
+ "mod_epicloot_config_weightperbonus": "Waga na premię",
+ "mod_epicloot_config_eitrcostperdamage": "Koszt eitru na obrażenie",
+ "mod_epicloot_config_minskilllevel": "Min. poziom umiejętności",
+ "mod_epicloot_config_levelweightexponent": "Wykładnik wagi poziomu",
+ "mod_epicloot_config_maxlevelupsperproc": "Maks. awansów na aktywację",
+ "mod_epicloot_config_maxmultiplier": "Maks. mnożnik łupu",
+ "mod_epicloot_config_maxunitsperentry": "Maks. jednostek na wpis",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Maks. dodanych jednostek na zabójstwo",
+ "mod_epicloot_config_rollbase": "Baza losowania",
+ "mod_epicloot_config_rollpervalue": "Losowanie na wartość",
+ "mod_epicloot_config_rollcurve": "Krzywa losowania",
+ "mod_epicloot_config_floorfraction": "Ułamek dolnej granicy",
+ "mod_epicloot_config_chitin": "Chityna",
+ "mod_epicloot_config_copperscrap": "Miedziany złom",
+ "mod_epicloot_config_ironscrap": "Żelazny złom",
+ "mod_epicloot_config_runestoneepic": "Epicki kamień runiczny",
+ "mod_epicloot_config_runestonelegendary": "Legendarny kamień runiczny",
"mod_epicloot_me_modifylowhealth_config_max": "Maksymalny próg niskiego zdrowia",
"mod_epicloot_me_riches_config_coins": "Wartość monet",
"mod_epicloot_me_riches_config_amber": "Wartość bursztynu",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(na stałe)",
"mod_epicloot_legendary_brokkrgift": "Legendarny dar Brokkra",
"mod_epicloot_mythic_brokkrgift": "Mityczny dar Brokkra",
+ "mod_epicloot_ancient_brokkrgift": "Starożytny dar Brokkra",
"mod_epicloot_slotchisel_introduce": "Niezwykle potężny artefakt, o którym mówi się, że osadza gniazdo odłamka w dowolnym magicznym przedmiocie.",
"mod_epicloot_slotchisel_hint": "Przeciągnij na magiczny przedmiot, aby dodać gniazdo odłamka",
"mod_epicloot_slotchisel_confirm_title": "Dodać gniazdo odłamka?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Każdy przewrót unikowy usuwa {0} sekund z twoich efektów trucizny i podpalenia.",
"mod_epicloot_me_stormrider_display": "+{0} % prędkości ruchu podczas burz",
"mod_epicloot_me_stormrider_desc": "+{0} % prędkości ruchu, gdy szaleje burza.",
- "mod_epicloot_me_shockingcharge_display": "Porażający ładunek",
- "mod_epicloot_me_shockingcharge_desc": "Twoje trafienia w walce gromadzą ładunki. Przy pełnym ładunku następne trafienie uwalnia skierowaną do przodu falę uderzeniową błyskawic zadającą część obrażeń twoich poprzednich trafień.",
- "mod_epicloot_me_forestsaid_display": "Pomoc lasu",
- "mod_epicloot_me_forestsaid_desc": "Gdy zostaniesz trafiony, wybuchają pętające korzenie unieruchamiające pobliskich wrogów.",
- "mod_epicloot_me_corpserot_display": "Trupi rozkład",
- "mod_epicloot_me_corpserot_desc": "Zabijani przez ciebie wrogowie mogą rozprysnąć się chmurą jadu, zatruwając pobliskich przeciwników.",
- "mod_epicloot_me_icyretribution_display": "Lodowa odpłata",
- "mod_epicloot_me_icyretribution_desc": "Gdy zostaniesz trafiony, wywołujesz wokół siebie mroźną nową. Czas odnowienia skaluje się z rzadkością.",
+ "mod_epicloot_me_shockingcharge_display": "Porażający ładunek: co {0:0.#} trafień",
+ "mod_epicloot_me_shockingcharge_desc": "Co {0:0.#} trafień w walce następne trafienie uwalnia skierowaną do przodu falę uderzeniową błyskawic, zadającą {1:0.#}% obrażeń tych trafień. Wyższa rzadkość wymaga mniej trafień.",
+ "mod_epicloot_me_forestsaid_display": "Pomoc lasu: {0:0.#} m, {1:0.#} s odnowienia",
+ "mod_epicloot_me_forestsaid_desc": "Gdy zostaniesz trafiony, wybuchają pętające korzenie unieruchamiające wrogów w promieniu {0:0.#} metrów. Wyższa rzadkość sięga dalej i odnawia się w {1:0.#} sekund.",
+ "mod_epicloot_me_corpserot_display": "Trupi rozkład: {0:0.#} trucizny",
+ "mod_epicloot_me_corpserot_desc": "Zabijani przez ciebie wrogowie mogą rozprysnąć się chmurą jadu, zadając pobliskim przeciwnikom {0:0.#} obrażeń od trucizny.",
+ "mod_epicloot_me_icyretribution_display": "Lodowa odpłata: {0:0.#} mrozu",
+ "mod_epicloot_me_icyretribution_desc": "Gdy zostaniesz trafiony, wywołujesz wokół siebie mroźną nową zadającą {0:0.#} obrażeń od mrozu. Wyższa rzadkość uderza mocniej i odnawia się szybciej.",
"mod_epicloot_me_meteorsummoner_display": "Przyzywacz meteorów: {0}× obrażeń trafienia",
"mod_epicloot_me_meteorsummoner_desc": "Co {1} trafień bronią twoje następne trafienie sprowadza na cel ognisty meteor zadający {0}× obrażeń tego trafienia jako ogień.",
- "mod_epicloot_me_everflow_display": "Wieczny przepływ Królowej",
- "mod_epicloot_me_everflow_desc": "Zabicie stworzenia daje kumulujące się wzmocnienie zwiększające regenerację zdrowia, wytrzymałości i eitru.",
- "mod_epicloot_me_necroticfire_display": "Nekrotyczny ogień",
- "mod_epicloot_me_necroticfire_desc": "Odbiera twojej broni wszystkie obrażenia fizyczne i dodaje tę pełną wartość zarówno jako obrażenia od ognia, jak i od trucizny.",
+ "mod_epicloot_me_everflow_display": "Wieczny przepływ Królowej: +{0:0.#}% regeneracji na warstwę",
+ "mod_epicloot_me_everflow_desc": "Zabicie stworzenia daje kumulujące się wzmocnienie zwiększające regenerację zdrowia, wytrzymałości i eitru o {0:0.#}% na warstwę, do {1:0.#} warstw.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotyczny ogień: +{0:0.#}% ognia i trucizny",
+ "mod_epicloot_me_necroticfire_desc": "Odbiera twojej broni wszystkie obrażenia fizyczne i dodaje {0:0.#}% z nich jako obrażenia od ognia oraz tyle samo jako obrażenia od trucizny.",
"mod_epicloot_circlet": "opaska",
"mod_epicloot_belt": "pasek",
"mod_epicloot_remains": "pozostałości",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendarny {0}",
"mod_epicloot_basicmythicnameformat": "Mitologiczny {0}",
+ "mod_epicloot_basicancientnameformat": "Starożytny {0}",
"mod_epicloot_rare_prefix1": "Dziwny",
"mod_epicloot_rare_prefix2": "Rzadki",
"mod_epicloot_rare_prefix3": "Tajemniczy",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "ki",
"mod_epicloot_Legendary": "Legendarny",
"mod_epicloot_Mythic": "Mityczny",
+ "mod_epicloot_Ancient": "Starożytny",
"mod_epicloot_search_placeholder": "Szukaj...",
"mod_epicloot_no_active_adventures": "Brak aktywnych przygód",
"mod_epicloot_set_bonuses": "Bonusy zestawu",
diff --git a/EpicLoot/localizations/Portuguese_Brazilian.json b/EpicLoot/localizations/Portuguese_Brazilian.json
index 6ae8662de..c9605b657 100644
--- a/EpicLoot/localizations/Portuguese_Brazilian.json
+++ b/EpicLoot/localizations/Portuguese_Brazilian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Sílex",
"mod_epicloot_config_serpentscale": "Escama de serpente",
"mod_epicloot_config_triplechance": "Chance de pesca tripla",
- "mod_epicloot_config_secondspercent": "Segundos por 1 %",
+ "mod_epicloot_config_secondsperpercent": "Segundos por 1 %",
"mod_epicloot_config_procchance": "Chance de ativação",
"mod_epicloot_config_bonusrollsmin": "Rolagens de saque bônus mín.",
"mod_epicloot_config_bonusrollsmax": "Rolagens de saque bônus máx.",
+ "mod_epicloot_config_cooldown": "Recarga (s)",
+ "mod_epicloot_config_basecooldown": "Recarga base (s)",
+ "mod_epicloot_config_cooldownperrarity": "Recarga por raridade (s)",
+ "mod_epicloot_config_cooldownpervalue": "Recarga por valor (s)",
+ "mod_epicloot_config_buffduration": "Duração (s)",
+ "mod_epicloot_config_tickinterval": "Intervalo de tique (s)",
+ "mod_epicloot_config_radius": "Raio (m)",
+ "mod_epicloot_config_baseradius": "Raio base (m)",
+ "mod_epicloot_config_radiuspertier": "Raio por valor (m)",
+ "mod_epicloot_config_maxcharges": "Golpes até carregar",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Golpes até carregar (base)",
+ "mod_epicloot_config_chargespervalue": "Golpes economizados por valor",
+ "mod_epicloot_config_mincharges": "Mín. de golpes até carregar",
+ "mod_epicloot_config_mincooldown": "Recarga mín. (s)",
+ "mod_epicloot_config_damagepertrigger": "Dano por acionamento",
+ "mod_epicloot_config_eitrpertrigger": "Eitr por acionamento",
+ "mod_epicloot_config_damagemultiplier": "Multiplicador de dano",
+ "mod_epicloot_config_damagefraction": "Fração de dano",
+ "mod_epicloot_config_damagepervalue": "Dano por valor",
+ "mod_epicloot_config_frostpertier": "Dano de gelo por valor",
+ "mod_epicloot_config_poisonpertier": "Dano de veneno por valor",
+ "mod_epicloot_config_spawnheight": "Altura de surgimento (m)",
+ "mod_epicloot_config_spawnradius": "Raio de surgimento (m)",
+ "mod_epicloot_config_spawninterval": "Intervalo de surgimento (s)",
+ "mod_epicloot_config_mindistance": "Distância mín. de lançamento (m)",
+ "mod_epicloot_config_maxdistance": "Distância máx. de lançamento (m)",
+ "mod_epicloot_config_projectilespeed": "Velocidade do projétil (m/s)",
+ "mod_epicloot_config_explosionradius": "Raio da explosão (m)",
+ "mod_epicloot_config_conelength": "Comprimento do cone (m)",
+ "mod_epicloot_config_conemaxwidth": "Largura do cone (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Janela de ignorar descarga (s)",
+ "mod_epicloot_config_minmovespeed": "Velocidade de movimento mín.",
+ "mod_epicloot_config_patchradius": "Raio da área (m)",
+ "mod_epicloot_config_patchlifetime": "Duração da área (s)",
+ "mod_epicloot_config_patchtickinterval": "Intervalo de tique da área (s)",
+ "mod_epicloot_config_speedfloor": "Piso de velocidade",
+ "mod_epicloot_config_batlifetime": "Duração do morcego (s)",
+ "mod_epicloot_config_summoncountoffset": "Deslocamento da contagem de invocações",
+ "mod_epicloot_config_concurrentpersummon": "Máx. simultâneos por invocação",
+ "mod_epicloot_config_maxreduction": "Redução máx.",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% da vida máx. por valor",
+ "mod_epicloot_config_minhealthreduction": "Redução de vida mín.",
+ "mod_epicloot_config_carryweightpervalue": "Capacidade de carga por valor",
+ "mod_epicloot_config_minresultingcarryweight": "Capacidade de carga mín.",
+ "mod_epicloot_config_weightthreshold": "Limiar de peso",
+ "mod_epicloot_config_weightperbonus": "Peso por bônus",
+ "mod_epicloot_config_eitrcostperdamage": "Custo de eitr por dano",
+ "mod_epicloot_config_minskilllevel": "Nível de perícia mín.",
+ "mod_epicloot_config_levelweightexponent": "Expoente de peso do nível",
+ "mod_epicloot_config_maxlevelupsperproc": "Máx. de subidas de nível por acionamento",
+ "mod_epicloot_config_maxmultiplier": "Multiplicador de saque máx.",
+ "mod_epicloot_config_maxunitsperentry": "Máx. de unidades por entrada",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Máx. de unidades adicionadas por abate",
+ "mod_epicloot_config_rollbase": "Base da rolagem",
+ "mod_epicloot_config_rollpervalue": "Rolagem por valor",
+ "mod_epicloot_config_rollcurve": "Curva da rolagem",
+ "mod_epicloot_config_floorfraction": "Fração do piso",
+ "mod_epicloot_config_chitin": "Quitina",
+ "mod_epicloot_config_copperscrap": "Restos de cobre",
+ "mod_epicloot_config_ironscrap": "Restos de ferro",
+ "mod_epicloot_config_runestoneepic": "Pedra rúnica épica",
+ "mod_epicloot_config_runestonelegendary": "Pedra rúnica lendária",
"mod_epicloot_me_modifylowhealth_config_max": "Limiar máximo de vida baixa",
"mod_epicloot_me_riches_config_coins": "Valor das moedas",
"mod_epicloot_me_riches_config_amber": "Valor do âmbar",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanente)",
"mod_epicloot_legendary_brokkrgift": "Dádiva de Brokkr lendária",
"mod_epicloot_mythic_brokkrgift": "Dádiva de Brokkr mítica",
+ "mod_epicloot_ancient_brokkrgift": "Dádiva de Brokkr antiga",
"mod_epicloot_slotchisel_introduce": "Um artefato extremamente poderoso, que dizem ser capaz de embutir um encaixe de fragmento em qualquer item mágico.",
"mod_epicloot_slotchisel_hint": "Arraste sobre um item mágico para adicionar um encaixe de fragmento",
"mod_epicloot_slotchisel_confirm_title": "Adicionar encaixe de fragmento?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Cada rolamento de esquiva remove {0} segundos dos seus efeitos de veneno e queimadura.",
"mod_epicloot_me_stormrider_display": "+{0} % de velocidade de movimento em tempestades",
"mod_epicloot_me_stormrider_desc": "+{0} % de velocidade de movimento enquanto uma tempestade ruge.",
- "mod_epicloot_me_shockingcharge_display": "Carga chocante",
- "mod_epicloot_me_shockingcharge_desc": "Seus golpes em combate acumulam cargas. Na carga máxima, o próximo golpe libera uma onda de choque elétrica à frente que causa uma parte do dano dos seus golpes anteriores.",
- "mod_epicloot_me_forestsaid_display": "Auxílio da floresta",
- "mod_epicloot_me_forestsaid_desc": "Quando você é atingido, raízes emaranhadas irrompem e imobilizam os inimigos próximos.",
- "mod_epicloot_me_corpserot_display": "Podridão cadavérica",
- "mod_epicloot_me_corpserot_desc": "Inimigos que você mata podem explodir em uma nuvem de veneno, envenenando os adversários próximos.",
- "mod_epicloot_me_icyretribution_display": "Retribuição gélida",
- "mod_epicloot_me_icyretribution_desc": "Quando você é atingido, detona uma nova gélida ao seu redor. A recarga escala com a raridade.",
+ "mod_epicloot_me_shockingcharge_display": "Carga chocante: a cada {0:0.#} golpes",
+ "mod_epicloot_me_shockingcharge_desc": "A cada {0:0.#} golpes em combate, o próximo golpe libera uma onda de choque elétrica à frente que causa {1:0.#}% do dano desses golpes. Uma raridade maior exige menos golpes.",
+ "mod_epicloot_me_forestsaid_display": "Auxílio da floresta: {0:0.#} m, {1:0.#} s de recarga",
+ "mod_epicloot_me_forestsaid_desc": "Quando você é atingido, raízes emaranhadas irrompem e imobilizam os inimigos a até {0:0.#} metros. Uma raridade maior alcança mais longe e recarrega em {1:0.#} segundos.",
+ "mod_epicloot_me_corpserot_display": "Podridão cadavérica: {0:0.#} de veneno",
+ "mod_epicloot_me_corpserot_desc": "Inimigos que você mata podem explodir em uma nuvem de veneno, causando {0:0.#} de dano de veneno aos adversários próximos.",
+ "mod_epicloot_me_icyretribution_display": "Retribuição gélida: {0:0.#} de gelo",
+ "mod_epicloot_me_icyretribution_desc": "Quando você é atingido, detona uma nova gélida ao seu redor que causa {0:0.#} de dano de gelo. Uma raridade maior bate mais forte e recarrega mais rápido.",
"mod_epicloot_me_meteorsummoner_display": "Invocador de meteoros: ×{0} do dano do golpe",
"mod_epicloot_me_meteorsummoner_desc": "A cada {1} golpes de arma, seu próximo golpe faz cair um meteoro flamejante sobre o alvo, causando {0} vezes o dano daquele golpe como fogo.",
- "mod_epicloot_me_everflow_display": "Fluxo eterno da Rainha",
- "mod_epicloot_me_everflow_desc": "Matar uma criatura concede um bônus acumulável que aumenta sua regeneração de vida, estamina e eitr.",
- "mod_epicloot_me_necroticfire_display": "Fogo necrótico",
- "mod_epicloot_me_necroticfire_desc": "Retira todo o dano físico da sua arma e adiciona esse valor integral tanto como dano de fogo quanto de veneno.",
+ "mod_epicloot_me_everflow_display": "Fluxo eterno da Rainha: +{0:0.#}% de regeneração por acúmulo",
+ "mod_epicloot_me_everflow_desc": "Matar uma criatura concede um bônus acumulável que aumenta sua regeneração de vida, estamina e eitr em {0:0.#}% por acúmulo, até {1:0.#} acúmulos.",
+ "mod_epicloot_me_necroticfire_display": "Fogo necrótico: +{0:0.#}% de fogo e veneno",
+ "mod_epicloot_me_necroticfire_desc": "Retira todo o dano físico da sua arma e adiciona {0:0.#}% dele como dano de fogo e o mesmo tanto como dano de veneno.",
"mod_epicloot_circlet": "diadema",
"mod_epicloot_belt": "cinto",
"mod_epicloot_remains": "restos",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Lendário {0}",
"mod_epicloot_basicmythicnameformat": "Mítico {0}",
+ "mod_epicloot_basicancientnameformat": "Antigo {0}",
"mod_epicloot_rare_prefix1": "Estranho",
"mod_epicloot_rare_prefix2": "Raro",
"mod_epicloot_rare_prefix3": "Críptico",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Épico",
"mod_epicloot_Legendary": "Legendário",
"mod_epicloot_Mythic": "Mítico",
+ "mod_epicloot_Ancient": "Antigo",
"mod_epicloot_search_placeholder": "Pesquisar...",
"mod_epicloot_no_active_adventures": "Nenhuma aventura ativa",
"mod_epicloot_set_bonuses": "Bônus de conjunto",
diff --git a/EpicLoot/localizations/Portuguese_European.json b/EpicLoot/localizations/Portuguese_European.json
index 19c5776e6..f51e374f3 100644
--- a/EpicLoot/localizations/Portuguese_European.json
+++ b/EpicLoot/localizations/Portuguese_European.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Sílex",
"mod_epicloot_config_serpentscale": "Escama de serpente",
"mod_epicloot_config_triplechance": "Probabilidade de captura tripla",
- "mod_epicloot_config_secondspercent": "Segundos por 1 %",
+ "mod_epicloot_config_secondsperpercent": "Segundos por 1 %",
"mod_epicloot_config_procchance": "Probabilidade de ativação",
"mod_epicloot_config_bonusrollsmin": "Lançamentos de espólio bónus mín.",
"mod_epicloot_config_bonusrollsmax": "Lançamentos de espólio bónus máx.",
+ "mod_epicloot_config_cooldown": "Recarga (s)",
+ "mod_epicloot_config_basecooldown": "Recarga base (s)",
+ "mod_epicloot_config_cooldownperrarity": "Recarga por raridade (s)",
+ "mod_epicloot_config_cooldownpervalue": "Recarga por valor (s)",
+ "mod_epicloot_config_buffduration": "Duração (s)",
+ "mod_epicloot_config_tickinterval": "Intervalo de tique (s)",
+ "mod_epicloot_config_radius": "Raio (m)",
+ "mod_epicloot_config_baseradius": "Raio base (m)",
+ "mod_epicloot_config_radiuspertier": "Raio por valor (m)",
+ "mod_epicloot_config_maxcharges": "Golpes até carregar",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Golpes até carregar (base)",
+ "mod_epicloot_config_chargespervalue": "Golpes poupados por valor",
+ "mod_epicloot_config_mincharges": "Mín. de golpes até carregar",
+ "mod_epicloot_config_mincooldown": "Recarga mín. (s)",
+ "mod_epicloot_config_damagepertrigger": "Dano por ativação",
+ "mod_epicloot_config_eitrpertrigger": "Eitr por ativação",
+ "mod_epicloot_config_damagemultiplier": "Multiplicador de dano",
+ "mod_epicloot_config_damagefraction": "Fração de dano",
+ "mod_epicloot_config_damagepervalue": "Dano por valor",
+ "mod_epicloot_config_frostpertier": "Dano de gelo por valor",
+ "mod_epicloot_config_poisonpertier": "Dano de veneno por valor",
+ "mod_epicloot_config_spawnheight": "Altura de surgimento (m)",
+ "mod_epicloot_config_spawnradius": "Raio de surgimento (m)",
+ "mod_epicloot_config_spawninterval": "Intervalo de surgimento (s)",
+ "mod_epicloot_config_mindistance": "Distância mín. de lançamento (m)",
+ "mod_epicloot_config_maxdistance": "Distância máx. de lançamento (m)",
+ "mod_epicloot_config_projectilespeed": "Velocidade do projétil (m/s)",
+ "mod_epicloot_config_explosionradius": "Raio da explosão (m)",
+ "mod_epicloot_config_conelength": "Comprimento do cone (m)",
+ "mod_epicloot_config_conemaxwidth": "Largura do cone (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Janela para ignorar descarga (s)",
+ "mod_epicloot_config_minmovespeed": "Velocidade de movimento mín.",
+ "mod_epicloot_config_patchradius": "Raio da área (m)",
+ "mod_epicloot_config_patchlifetime": "Duração da área (s)",
+ "mod_epicloot_config_patchtickinterval": "Intervalo de tique da área (s)",
+ "mod_epicloot_config_speedfloor": "Velocidade mínima",
+ "mod_epicloot_config_batlifetime": "Duração do morcego (s)",
+ "mod_epicloot_config_summoncountoffset": "Desvio da contagem de invocações",
+ "mod_epicloot_config_concurrentpersummon": "Máx. simultâneos por invocação",
+ "mod_epicloot_config_maxreduction": "Redução máx.",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% da vida máx. por valor",
+ "mod_epicloot_config_minhealthreduction": "Redução de vida mín.",
+ "mod_epicloot_config_carryweightpervalue": "Capacidade de carga por valor",
+ "mod_epicloot_config_minresultingcarryweight": "Capacidade de carga mín.",
+ "mod_epicloot_config_weightthreshold": "Limiar de peso",
+ "mod_epicloot_config_weightperbonus": "Peso por bónus",
+ "mod_epicloot_config_eitrcostperdamage": "Custo de eitr por dano",
+ "mod_epicloot_config_minskilllevel": "Nível de perícia mín.",
+ "mod_epicloot_config_levelweightexponent": "Expoente de peso do nível",
+ "mod_epicloot_config_maxlevelupsperproc": "Máx. de subidas de nível por ativação",
+ "mod_epicloot_config_maxmultiplier": "Multiplicador de espólio máx.",
+ "mod_epicloot_config_maxunitsperentry": "Máx. de unidades por entrada",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Máx. de unidades adicionadas por abate",
+ "mod_epicloot_config_rollbase": "Base do lançamento",
+ "mod_epicloot_config_rollpervalue": "Lançamento por valor",
+ "mod_epicloot_config_rollcurve": "Curva do lançamento",
+ "mod_epicloot_config_floorfraction": "Fração mínima",
+ "mod_epicloot_config_chitin": "Quitina",
+ "mod_epicloot_config_copperscrap": "Sucata de cobre",
+ "mod_epicloot_config_ironscrap": "Ferro-velho",
+ "mod_epicloot_config_runestoneepic": "Runa épica",
+ "mod_epicloot_config_runestonelegendary": "Runa lendária",
"mod_epicloot_me_modifylowhealth_config_max": "Limiar máximo de vida baixa",
"mod_epicloot_me_riches_config_coins": "Valor das moedas",
"mod_epicloot_me_riches_config_amber": "Valor do âmbar",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanente)",
"mod_epicloot_legendary_brokkrgift": "Dádiva de Brokkr lendária",
"mod_epicloot_mythic_brokkrgift": "Dádiva de Brokkr mítica",
+ "mod_epicloot_ancient_brokkrgift": "Dádiva de Brokkr antiga",
"mod_epicloot_slotchisel_introduce": "Um artefacto extremamente poderoso, que se diz ser capaz de embutir um encaixe de fragmento em qualquer item mágico.",
"mod_epicloot_slotchisel_hint": "Arrasta sobre um item mágico para adicionar um encaixe de fragmento",
"mod_epicloot_slotchisel_confirm_title": "Adicionar encaixe de fragmento?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Cada rolamento de esquiva remove {0} segundos dos teus efeitos de veneno e queimadura.",
"mod_epicloot_me_stormrider_display": "+{0} % de velocidade de movimento em tempestades",
"mod_epicloot_me_stormrider_desc": "+{0} % de velocidade de movimento enquanto uma tempestade ruge.",
- "mod_epicloot_me_shockingcharge_display": "Carga fulminante",
- "mod_epicloot_me_shockingcharge_desc": "Os teus golpes em combate acumulam cargas. Na carga máxima, o golpe seguinte liberta uma onda de choque elétrica em frente que causa uma parte do dano dos teus golpes anteriores.",
- "mod_epicloot_me_forestsaid_display": "Auxílio da floresta",
- "mod_epicloot_me_forestsaid_desc": "Quando és atingido, raízes emaranhadas irrompem e imobilizam os inimigos próximos.",
- "mod_epicloot_me_corpserot_display": "Podridão cadavérica",
- "mod_epicloot_me_corpserot_desc": "Os inimigos que matas podem rebentar numa nuvem de veneno, envenenando os adversários próximos.",
- "mod_epicloot_me_icyretribution_display": "Retribuição gélida",
- "mod_epicloot_me_icyretribution_desc": "Quando és atingido, detonas uma nova gélida à tua volta. A recarga escala com a raridade.",
+ "mod_epicloot_me_shockingcharge_display": "Carga fulminante: a cada {0:0.#} golpes",
+ "mod_epicloot_me_shockingcharge_desc": "A cada {0:0.#} golpes em combate, o golpe seguinte liberta uma onda de choque elétrica em frente que causa {1:0.#}% do dano desses golpes. Uma raridade maior exige menos golpes.",
+ "mod_epicloot_me_forestsaid_display": "Auxílio da floresta: {0:0.#} m, {1:0.#} s de recarga",
+ "mod_epicloot_me_forestsaid_desc": "Quando és atingido, raízes emaranhadas irrompem e imobilizam os inimigos a até {0:0.#} metros. Uma raridade maior alcança mais longe e recarrega em {1:0.#} segundos.",
+ "mod_epicloot_me_corpserot_display": "Podridão cadavérica: {0:0.#} de veneno",
+ "mod_epicloot_me_corpserot_desc": "Os inimigos que matas podem rebentar numa nuvem de veneno, causando {0:0.#} de dano de veneno aos adversários próximos.",
+ "mod_epicloot_me_icyretribution_display": "Retribuição gélida: {0:0.#} de gelo",
+ "mod_epicloot_me_icyretribution_desc": "Quando és atingido, detonas uma nova gélida à tua volta que causa {0:0.#} de dano de gelo. Uma raridade maior bate mais forte e recarrega mais depressa.",
"mod_epicloot_me_meteorsummoner_display": "Invocador de meteoros: ×{0} do dano do golpe",
"mod_epicloot_me_meteorsummoner_desc": "A cada {1} golpes de arma, o teu golpe seguinte faz cair um meteoro flamejante sobre o alvo, causando {0} vezes o dano desse golpe como fogo.",
- "mod_epicloot_me_everflow_display": "Fluxo eterno da Rainha",
- "mod_epicloot_me_everflow_desc": "Matar uma criatura concede um bónus acumulável que aumenta a tua regeneração de vida, vigor e eitr.",
- "mod_epicloot_me_necroticfire_display": "Fogo necrótico",
- "mod_epicloot_me_necroticfire_desc": "Retira todo o dano físico da tua arma e adiciona esse valor integral tanto como dano de fogo como de veneno.",
+ "mod_epicloot_me_everflow_display": "Fluxo eterno da Rainha: +{0:0.#}% de regeneração por acumulação",
+ "mod_epicloot_me_everflow_desc": "Matar uma criatura concede um bónus acumulável que aumenta a tua regeneração de vida, vigor e eitr em {0:0.#}% por acumulação, até {1:0.#} acumulações.",
+ "mod_epicloot_me_necroticfire_display": "Fogo necrótico: +{0:0.#}% de fogo e veneno",
+ "mod_epicloot_me_necroticfire_desc": "Retira todo o dano físico da tua arma e adiciona {0:0.#}% dele como dano de fogo e o mesmo tanto como dano de veneno.",
"mod_epicloot_circlet": "coroa",
"mod_epicloot_belt": "cinto",
"mod_epicloot_remains": "restos",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Lendário {0}",
"mod_epicloot_basicmythicnameformat": "Mítico {0}",
+ "mod_epicloot_basicancientnameformat": "Antigo {0}",
"mod_epicloot_rare_prefix1": "Estranho",
"mod_epicloot_rare_prefix2": "Raro",
"mod_epicloot_rare_prefix3": "Críptico",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Épico",
"mod_epicloot_Legendary": "Legendário",
"mod_epicloot_Mythic": "Mítico",
+ "mod_epicloot_Ancient": "Antigo",
"mod_epicloot_search_placeholder": "Pesquisar...",
"mod_epicloot_no_active_adventures": "Nenhuma aventura ativa",
"mod_epicloot_set_bonuses": "Bónus de conjunto",
diff --git a/EpicLoot/localizations/Romanian.json b/EpicLoot/localizations/Romanian.json
index 98fd656fc..581d1ec54 100644
--- a/EpicLoot/localizations/Romanian.json
+++ b/EpicLoot/localizations/Romanian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Cremene",
"mod_epicloot_config_serpentscale": "Solz de șarpe de mare",
"mod_epicloot_config_triplechance": "Șansă de captură triplă",
- "mod_epicloot_config_secondspercent": "Secunde per 1 %",
+ "mod_epicloot_config_secondsperpercent": "Secunde per 1 %",
"mod_epicloot_config_procchance": "Șansă de declanșare",
"mod_epicloot_config_bonusrollsmin": "Min. aruncări bonus de pradă",
"mod_epicloot_config_bonusrollsmax": "Max. aruncări bonus de pradă",
+ "mod_epicloot_config_cooldown": "Reîncărcare (s)",
+ "mod_epicloot_config_basecooldown": "Reîncărcare de bază (s)",
+ "mod_epicloot_config_cooldownperrarity": "Reîncărcare per raritate (s)",
+ "mod_epicloot_config_cooldownpervalue": "Reîncărcare per valoare (s)",
+ "mod_epicloot_config_buffduration": "Durată (s)",
+ "mod_epicloot_config_tickinterval": "Interval de tact (s)",
+ "mod_epicloot_config_radius": "Rază (m)",
+ "mod_epicloot_config_baseradius": "Rază de bază (m)",
+ "mod_epicloot_config_radiuspertier": "Rază per valoare (m)",
+ "mod_epicloot_config_maxcharges": "Lovituri până la încărcare",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Lovituri până la încărcare (bază)",
+ "mod_epicloot_config_chargespervalue": "Lovituri economisite per valoare",
+ "mod_epicloot_config_mincharges": "Min. lovituri până la încărcare",
+ "mod_epicloot_config_mincooldown": "Reîncărcare min. (s)",
+ "mod_epicloot_config_damagepertrigger": "Daune per declanșare",
+ "mod_epicloot_config_eitrpertrigger": "Eitr per declanșare",
+ "mod_epicloot_config_damagemultiplier": "Multiplicator de daune",
+ "mod_epicloot_config_damagefraction": "Fracțiune din daune",
+ "mod_epicloot_config_damagepervalue": "Daune per valoare",
+ "mod_epicloot_config_frostpertier": "Daune de ger per valoare",
+ "mod_epicloot_config_poisonpertier": "Daune de otravă per valoare",
+ "mod_epicloot_config_spawnheight": "Înălțime de apariție (m)",
+ "mod_epicloot_config_spawnradius": "Rază de apariție (m)",
+ "mod_epicloot_config_spawninterval": "Interval de apariție (s)",
+ "mod_epicloot_config_mindistance": "Distanță min. de lansare (m)",
+ "mod_epicloot_config_maxdistance": "Distanță max. de lansare (m)",
+ "mod_epicloot_config_projectilespeed": "Viteza proiectilului (m/s)",
+ "mod_epicloot_config_explosionradius": "Raza exploziei (m)",
+ "mod_epicloot_config_conelength": "Lungimea conului (m)",
+ "mod_epicloot_config_conemaxwidth": "Lățimea conului (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Fereastră de ignorare a descărcării (s)",
+ "mod_epicloot_config_minmovespeed": "Viteză de deplasare min.",
+ "mod_epicloot_config_patchradius": "Raza zonei (m)",
+ "mod_epicloot_config_patchlifetime": "Durata zonei (s)",
+ "mod_epicloot_config_patchtickinterval": "Interval de tact al zonei (s)",
+ "mod_epicloot_config_speedfloor": "Viteză minimă",
+ "mod_epicloot_config_batlifetime": "Durata liliacului (s)",
+ "mod_epicloot_config_summoncountoffset": "Decalajul numărului de invocări",
+ "mod_epicloot_config_concurrentpersummon": "Max. simultane per invocare",
+ "mod_epicloot_config_maxreduction": "Reducere max.",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% din viața max. per valoare",
+ "mod_epicloot_config_minhealthreduction": "Reducere min. de viață",
+ "mod_epicloot_config_carryweightpervalue": "Capacitate de transport per valoare",
+ "mod_epicloot_config_minresultingcarryweight": "Capacitate de transport min.",
+ "mod_epicloot_config_weightthreshold": "Prag de greutate",
+ "mod_epicloot_config_weightperbonus": "Greutate per bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Cost de eitr per daună",
+ "mod_epicloot_config_minskilllevel": "Nivel min. de abilitate",
+ "mod_epicloot_config_levelweightexponent": "Exponent de ponderare a nivelului",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. creșteri de nivel per declanșare",
+ "mod_epicloot_config_maxmultiplier": "Multiplicator max. de pradă",
+ "mod_epicloot_config_maxunitsperentry": "Max. unități per intrare",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. unități adăugate per ucidere",
+ "mod_epicloot_config_rollbase": "Baza aruncării",
+ "mod_epicloot_config_rollpervalue": "Aruncare per valoare",
+ "mod_epicloot_config_rollcurve": "Curba aruncării",
+ "mod_epicloot_config_floorfraction": "Fracțiunea minimă",
+ "mod_epicloot_config_chitin": "Chitină",
+ "mod_epicloot_config_copperscrap": "Cupru vechi",
+ "mod_epicloot_config_ironscrap": "Fier vechi",
+ "mod_epicloot_config_runestoneepic": "Piatră runică epică",
+ "mod_epicloot_config_runestonelegendary": "Piatră runică legendară",
"mod_epicloot_me_modifylowhealth_config_max": "Prag maxim de viață scăzută",
"mod_epicloot_me_riches_config_coins": "Valoarea monedelor",
"mod_epicloot_me_riches_config_amber": "Valoarea chihlimbarului",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanent)",
"mod_epicloot_legendary_brokkrgift": "Darul legendar al lui Brokkr",
"mod_epicloot_mythic_brokkrgift": "Darul mitic al lui Brokkr",
+ "mod_epicloot_ancient_brokkrgift": "Darul antic al lui Brokkr",
"mod_epicloot_slotchisel_introduce": "Un artefact extrem de puternic, despre care se spune că încastrează un locaș de ciob în orice obiect magic.",
"mod_epicloot_slotchisel_hint": "Trage-l peste un obiect magic pentru a adăuga un locaș de ciob",
"mod_epicloot_slotchisel_confirm_title": "Adaugi un locaș de ciob?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Fiecare rostogolire de eschivă elimină {0} secunde din efectele tale de otravă și ardere.",
"mod_epicloot_me_stormrider_display": "+{0} % viteză de deplasare în furtuni",
"mod_epicloot_me_stormrider_desc": "+{0} % viteză de deplasare cât timp bântuie o furtună.",
- "mod_epicloot_me_shockingcharge_display": "Încărcătură șocantă",
- "mod_epicloot_me_shockingcharge_desc": "Loviturile tale în luptă acumulează încărcături. La încărcătură maximă, următoarea lovitură eliberează o undă de șoc electrică înainte, care provoacă o parte din daunele loviturilor tale anterioare.",
- "mod_epicloot_me_forestsaid_display": "Ajutorul pădurii",
- "mod_epicloot_me_forestsaid_desc": "Când ești lovit, rădăcini încâlcite izbucnesc și imobilizează inamicii din apropiere.",
- "mod_epicloot_me_corpserot_display": "Putrezire cadaverică",
- "mod_epicloot_me_corpserot_desc": "Inamicii pe care îi ucizi pot exploda într-un nor de venin, otrăvind adversarii din apropiere.",
- "mod_epicloot_me_icyretribution_display": "Răzbunare înghețată",
- "mod_epicloot_me_icyretribution_desc": "Când ești lovit, detonezi o novă de ger în jurul tău. Reîncărcarea se scalează cu raritatea.",
+ "mod_epicloot_me_shockingcharge_display": "Încărcătură șocantă: la fiecare {0:0.#} lovituri",
+ "mod_epicloot_me_shockingcharge_desc": "La fiecare {0:0.#} lovituri în luptă, următoarea lovitură eliberează o undă de șoc electrică înainte, care provoacă {1:0.#}% din daunele acelor lovituri. O raritate mai mare cere mai puține lovituri.",
+ "mod_epicloot_me_forestsaid_display": "Ajutorul pădurii: {0:0.#} m, {1:0.#} s reîncărcare",
+ "mod_epicloot_me_forestsaid_desc": "Când ești lovit, rădăcini încâlcite izbucnesc și imobilizează inamicii pe o rază de {0:0.#} metri. O raritate mai mare ajunge mai departe și se reîncarcă în {1:0.#} secunde.",
+ "mod_epicloot_me_corpserot_display": "Putrezire cadaverică: {0:0.#} otravă",
+ "mod_epicloot_me_corpserot_desc": "Inamicii pe care îi ucizi pot exploda într-un nor de venin, provocând {0:0.#} daune de otravă adversarilor din apropiere.",
+ "mod_epicloot_me_icyretribution_display": "Răzbunare înghețată: {0:0.#} ger",
+ "mod_epicloot_me_icyretribution_desc": "Când ești lovit, detonezi o novă de ger în jurul tău care provoacă {0:0.#} daune de ger. O raritate mai mare lovește mai tare și se reîncarcă mai repede.",
"mod_epicloot_me_meteorsummoner_display": "Invocator de meteori: ×{0} daunele loviturii",
"mod_epicloot_me_meteorsummoner_desc": "La fiecare {1} lovituri cu arma, următoarea ta lovitură cheamă un meteor de foc asupra țintei, care provoacă de {0} ori daunele acelei lovituri sub formă de foc.",
- "mod_epicloot_me_everflow_display": "Curgerea veșnică a Reginei",
- "mod_epicloot_me_everflow_desc": "Uciderea unei creaturi acordă un bonus cumulativ care sporește regenerarea vieții, staminei și eitrului.",
- "mod_epicloot_me_necroticfire_display": "Foc necrotic",
- "mod_epicloot_me_necroticfire_desc": "Îndepărtează toate daunele fizice ale armei tale și adaugă întreaga valoare atât ca daune de foc, cât și de otravă.",
+ "mod_epicloot_me_everflow_display": "Curgerea veșnică a Reginei: +{0:0.#}% regenerare pe strat",
+ "mod_epicloot_me_everflow_desc": "Uciderea unei creaturi acordă un bonus cumulativ care sporește regenerarea vieții, staminei și eitrului cu {0:0.#}% pe strat, până la {1:0.#} straturi.",
+ "mod_epicloot_me_necroticfire_display": "Foc necrotic: +{0:0.#}% foc și otravă",
+ "mod_epicloot_me_necroticfire_desc": "Îndepărtează toate daunele fizice ale armei tale și adaugă {0:0.#}% din ele ca daune de foc și tot atât ca daune de otravă.",
"mod_epicloot_circlet": "cercel",
"mod_epicloot_belt": "curea",
"mod_epicloot_remains": "rămășițe",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendar {0}",
"mod_epicloot_basicmythicnameformat": "Mitic {0}",
+ "mod_epicloot_basicancientnameformat": "Antic {0}",
"mod_epicloot_rare_prefix1": "Ciudat",
"mod_epicloot_rare_prefix2": "Rar",
"mod_epicloot_rare_prefix3": "Criptic",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epopeic",
"mod_epicloot_Legendary": "Legendare",
"mod_epicloot_Mythic": "Mitic",
+ "mod_epicloot_Ancient": "Antic",
"mod_epicloot_search_placeholder": "Căutare...",
"mod_epicloot_no_active_adventures": "Nu există aventuri active",
"mod_epicloot_set_bonuses": "Bonusuri set",
diff --git a/EpicLoot/localizations/Russian.json b/EpicLoot/localizations/Russian.json
index 1c4130c34..8167f07d7 100644
--- a/EpicLoot/localizations/Russian.json
+++ b/EpicLoot/localizations/Russian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Кремень",
"mod_epicloot_config_serpentscale": "Чешуя морского змея",
"mod_epicloot_config_triplechance": "Шанс тройного улова",
- "mod_epicloot_config_secondspercent": "Секунд на 1 %",
+ "mod_epicloot_config_secondsperpercent": "Секунд на 1 %",
"mod_epicloot_config_procchance": "Шанс срабатывания",
"mod_epicloot_config_bonusrollsmin": "Мин. бонусных бросков добычи",
"mod_epicloot_config_bonusrollsmax": "Макс. бонусных бросков добычи",
+ "mod_epicloot_config_cooldown": "Перезарядка (с)",
+ "mod_epicloot_config_basecooldown": "Базовая перезарядка (с)",
+ "mod_epicloot_config_cooldownperrarity": "Перезарядка за редкость (с)",
+ "mod_epicloot_config_cooldownpervalue": "Перезарядка за значение (с)",
+ "mod_epicloot_config_buffduration": "Длительность (с)",
+ "mod_epicloot_config_tickinterval": "Интервал тика (с)",
+ "mod_epicloot_config_radius": "Радиус (м)",
+ "mod_epicloot_config_baseradius": "Базовый радиус (м)",
+ "mod_epicloot_config_radiuspertier": "Радиус за значение (м)",
+ "mod_epicloot_config_maxcharges": "Ударов до заряда",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Ударов до заряда (базово)",
+ "mod_epicloot_config_chargespervalue": "Сэкономлено ударов за значение",
+ "mod_epicloot_config_mincharges": "Мин. ударов до заряда",
+ "mod_epicloot_config_mincooldown": "Мин. перезарядка (с)",
+ "mod_epicloot_config_damagepertrigger": "Урон за срабатывание",
+ "mod_epicloot_config_eitrpertrigger": "Эйтр за срабатывание",
+ "mod_epicloot_config_damagemultiplier": "Множитель урона",
+ "mod_epicloot_config_damagefraction": "Доля урона",
+ "mod_epicloot_config_damagepervalue": "Урон за значение",
+ "mod_epicloot_config_frostpertier": "Урон холодом за значение",
+ "mod_epicloot_config_poisonpertier": "Урон ядом за значение",
+ "mod_epicloot_config_spawnheight": "Высота появления (м)",
+ "mod_epicloot_config_spawnradius": "Радиус появления (м)",
+ "mod_epicloot_config_spawninterval": "Интервал появления (с)",
+ "mod_epicloot_config_mindistance": "Мин. дальность запуска (м)",
+ "mod_epicloot_config_maxdistance": "Макс. дальность запуска (м)",
+ "mod_epicloot_config_projectilespeed": "Скорость снаряда (м/с)",
+ "mod_epicloot_config_explosionradius": "Радиус взрыва (м)",
+ "mod_epicloot_config_conelength": "Длина конуса (м)",
+ "mod_epicloot_config_conemaxwidth": "Ширина конуса (м)",
+ "mod_epicloot_config_dischargeignorewindow": "Окно игнорирования разряда (с)",
+ "mod_epicloot_config_minmovespeed": "Мин. скорость передвижения",
+ "mod_epicloot_config_patchradius": "Радиус области (м)",
+ "mod_epicloot_config_patchlifetime": "Время жизни области (с)",
+ "mod_epicloot_config_patchtickinterval": "Интервал тика области (с)",
+ "mod_epicloot_config_speedfloor": "Нижний предел скорости",
+ "mod_epicloot_config_batlifetime": "Время жизни летучей мыши (с)",
+ "mod_epicloot_config_summoncountoffset": "Смещение числа призванных",
+ "mod_epicloot_config_concurrentpersummon": "Макс. одновременно на призыв",
+ "mod_epicloot_config_maxreduction": "Макс. снижение",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% макс. здоровья за значение",
+ "mod_epicloot_config_minhealthreduction": "Мин. снижение здоровья",
+ "mod_epicloot_config_carryweightpervalue": "Грузоподъёмность за значение",
+ "mod_epicloot_config_minresultingcarryweight": "Мин. грузоподъёмность",
+ "mod_epicloot_config_weightthreshold": "Порог веса",
+ "mod_epicloot_config_weightperbonus": "Вес за бонус",
+ "mod_epicloot_config_eitrcostperdamage": "Расход эйтра за единицу урона",
+ "mod_epicloot_config_minskilllevel": "Мин. уровень навыка",
+ "mod_epicloot_config_levelweightexponent": "Показатель веса уровня",
+ "mod_epicloot_config_maxlevelupsperproc": "Макс. повышений уровня за срабатывание",
+ "mod_epicloot_config_maxmultiplier": "Макс. множитель добычи",
+ "mod_epicloot_config_maxunitsperentry": "Макс. единиц на запись",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Макс. добавленных единиц за убийство",
+ "mod_epicloot_config_rollbase": "Основа броска",
+ "mod_epicloot_config_rollpervalue": "Бросок за значение",
+ "mod_epicloot_config_rollcurve": "Кривая броска",
+ "mod_epicloot_config_floorfraction": "Доля нижнего предела",
+ "mod_epicloot_config_chitin": "Хитин",
+ "mod_epicloot_config_copperscrap": "Медный металлолом",
+ "mod_epicloot_config_ironscrap": "Металлолом",
+ "mod_epicloot_config_runestoneepic": "Эпический рунный камень",
+ "mod_epicloot_config_runestonelegendary": "Легендарный рунный камень",
"mod_epicloot_me_modifylowhealth_config_max": "Максимальный порог низкого здоровья",
"mod_epicloot_me_riches_config_coins": "Ценность монет",
"mod_epicloot_me_riches_config_amber": "Ценность янтаря",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(навсегда)",
"mod_epicloot_legendary_brokkrgift": "Легендарный дар Брокка",
"mod_epicloot_mythic_brokkrgift": "Мифический дар Брокка",
+ "mod_epicloot_ancient_brokkrgift": "Древний дар Брокка",
"mod_epicloot_slotchisel_introduce": "Чрезвычайно могущественный артефакт, который, как говорят, встраивает гнездо для осколков в любой магический предмет.",
"mod_epicloot_slotchisel_hint": "Перетащите на магический предмет, чтобы добавить гнездо для осколков",
"mod_epicloot_slotchisel_confirm_title": "Добавить гнездо для осколков?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Каждый перекат уклонения снимает {0} секунд с ваших эффектов яда и горения.",
"mod_epicloot_me_stormrider_display": "+{0} % к скорости передвижения в бурю",
"mod_epicloot_me_stormrider_desc": "+{0} % к скорости передвижения, пока бушует буря.",
- "mod_epicloot_me_shockingcharge_display": "Разящий заряд",
- "mod_epicloot_me_shockingcharge_desc": "Ваши удары в бою накапливают заряды. При полном заряде следующий удар выпускает вперёд ударную волну молний, наносящую часть урона ваших предыдущих ударов.",
- "mod_epicloot_me_forestsaid_display": "Помощь леса",
- "mod_epicloot_me_forestsaid_desc": "Когда вас бьют, из земли вырываются опутывающие корни и обездвиживают врагов поблизости.",
- "mod_epicloot_me_corpserot_display": "Трупная гниль",
- "mod_epicloot_me_corpserot_desc": "Убитые вами враги могут лопнуть облаком яда, отравляя противников поблизости.",
- "mod_epicloot_me_icyretribution_display": "Ледяное возмездие",
- "mod_epicloot_me_icyretribution_desc": "Когда вас бьют, вокруг вас взрывается ледяная нова. Перезарядка зависит от редкости.",
+ "mod_epicloot_me_shockingcharge_display": "Разящий заряд: каждые {0:0.#} ударов",
+ "mod_epicloot_me_shockingcharge_desc": "Каждые {0:0.#} ударов в бою следующий удар выпускает вперёд ударную волну молний, наносящую {1:0.#}% урона этих ударов. Более высокая редкость требует меньше ударов.",
+ "mod_epicloot_me_forestsaid_display": "Помощь леса: {0:0.#} м, перезарядка {1:0.#} с",
+ "mod_epicloot_me_forestsaid_desc": "Когда вас бьют, из земли вырываются опутывающие корни и обездвиживают врагов в радиусе {0:0.#} метров. Более высокая редкость бьёт дальше и перезаряжается за {1:0.#} секунд.",
+ "mod_epicloot_me_corpserot_display": "Трупная гниль: {0:0.#} яда",
+ "mod_epicloot_me_corpserot_desc": "Убитые вами враги могут лопнуть облаком яда, нанося противникам поблизости {0:0.#} урона ядом.",
+ "mod_epicloot_me_icyretribution_display": "Ледяное возмездие: {0:0.#} холода",
+ "mod_epicloot_me_icyretribution_desc": "Когда вас бьют, вокруг вас взрывается ледяная нова, наносящая {0:0.#} урона холодом. Более высокая редкость бьёт сильнее и перезаряжается быстрее.",
"mod_epicloot_me_meteorsummoner_display": "Призыватель метеоров: {0}× урона удара",
"mod_epicloot_me_meteorsummoner_desc": "Каждые {1} ударов оружием ваш следующий удар обрушивает на цель огненный метеор, наносящий {0}× урона того удара в виде огня.",
- "mod_epicloot_me_everflow_display": "Вечный поток Королевы",
- "mod_epicloot_me_everflow_desc": "Убийство существа даёт складывающийся эффект, повышающий восстановление здоровья, выносливости и эйтра.",
- "mod_epicloot_me_necroticfire_display": "Некротический огонь",
- "mod_epicloot_me_necroticfire_desc": "Снимает с вашего оружия весь физический урон и добавляет эту полную величину и как урон огнём, и как урон ядом.",
+ "mod_epicloot_me_everflow_display": "Вечный поток Королевы: +{0:0.#}% восстановления за стак",
+ "mod_epicloot_me_everflow_desc": "Убийство существа даёт складывающийся эффект, повышающий восстановление здоровья, выносливости и эйтра на {0:0.#}% за стак, до {1:0.#} стаков.",
+ "mod_epicloot_me_necroticfire_display": "Некротический огонь: +{0:0.#}% огня и яда",
+ "mod_epicloot_me_necroticfire_desc": "Снимает с вашего оружия весь физический урон и добавляет {0:0.#}% от него как урон огнём и столько же как урон ядом.",
"mod_epicloot_circlet": "венец",
"mod_epicloot_belt": "пояс",
"mod_epicloot_remains": "останки",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{1} {0}",
"mod_epicloot_basiclegendarynameformat": "Легендарн. {0}",
"mod_epicloot_basicmythicnameformat": "Мифическ. {0}",
+ "mod_epicloot_basicancientnameformat": "Древн. {0}",
"mod_epicloot_rare_prefix1": "Секретн.",
"mod_epicloot_rare_prefix2": "Редк.",
"mod_epicloot_rare_prefix3": "Загадочн.",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Эпическ.",
"mod_epicloot_Legendary": "Легендарн.",
"mod_epicloot_Mythic": "Мифическ.",
+ "mod_epicloot_Ancient": "Древн.",
"mod_epicloot_search_placeholder": "Поиск...",
"mod_epicloot_no_active_adventures": "Активных приключений нет",
"mod_epicloot_set_bonuses": "Бонусы комплекта",
diff --git a/EpicLoot/localizations/Serbian.json b/EpicLoot/localizations/Serbian.json
index 606eafd84..31b9d72c2 100644
--- a/EpicLoot/localizations/Serbian.json
+++ b/EpicLoot/localizations/Serbian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Кремен",
"mod_epicloot_config_serpentscale": "Крљушт морске змије",
"mod_epicloot_config_triplechance": "Шанса за троструки улов",
- "mod_epicloot_config_secondspercent": "Секунди по 1 %",
+ "mod_epicloot_config_secondsperpercent": "Секунди по 1 %",
"mod_epicloot_config_procchance": "Шанса активације",
"mod_epicloot_config_bonusrollsmin": "Мин. бонус бацања за плен",
"mod_epicloot_config_bonusrollsmax": "Макс. бонус бацања за плен",
+ "mod_epicloot_config_cooldown": "Одмор (с)",
+ "mod_epicloot_config_basecooldown": "Основни одмор (с)",
+ "mod_epicloot_config_cooldownperrarity": "Одмор по реткости (с)",
+ "mod_epicloot_config_cooldownpervalue": "Одмор по вредности (с)",
+ "mod_epicloot_config_buffduration": "Трајање (с)",
+ "mod_epicloot_config_tickinterval": "Интервал откуцаја (с)",
+ "mod_epicloot_config_radius": "Полупречник (м)",
+ "mod_epicloot_config_baseradius": "Основни полупречник (м)",
+ "mod_epicloot_config_radiuspertier": "Полупречник по вредности (м)",
+ "mod_epicloot_config_maxcharges": "Удараца до пуњења",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Удараца до пуњења (основно)",
+ "mod_epicloot_config_chargespervalue": "Уштеђених удараца по вредности",
+ "mod_epicloot_config_mincharges": "Мин. удараца до пуњења",
+ "mod_epicloot_config_mincooldown": "Мин. одмор (с)",
+ "mod_epicloot_config_damagepertrigger": "Штета по окидању",
+ "mod_epicloot_config_eitrpertrigger": "Еитр по окидању",
+ "mod_epicloot_config_damagemultiplier": "Множилац штете",
+ "mod_epicloot_config_damagefraction": "Удео штете",
+ "mod_epicloot_config_damagepervalue": "Штета по вредности",
+ "mod_epicloot_config_frostpertier": "Ледена штета по вредности",
+ "mod_epicloot_config_poisonpertier": "Отровна штета по вредности",
+ "mod_epicloot_config_spawnheight": "Висина појављивања (м)",
+ "mod_epicloot_config_spawnradius": "Полупречник појављивања (м)",
+ "mod_epicloot_config_spawninterval": "Интервал појављивања (с)",
+ "mod_epicloot_config_mindistance": "Мин. даљина избацивања (м)",
+ "mod_epicloot_config_maxdistance": "Макс. даљина избацивања (м)",
+ "mod_epicloot_config_projectilespeed": "Брзина пројектила (м/с)",
+ "mod_epicloot_config_explosionradius": "Полупречник експлозије (м)",
+ "mod_epicloot_config_conelength": "Дужина купе (м)",
+ "mod_epicloot_config_conemaxwidth": "Ширина купе (м)",
+ "mod_epicloot_config_dischargeignorewindow": "Прозор занемаривања пражњења (с)",
+ "mod_epicloot_config_minmovespeed": "Мин. брзина кретања",
+ "mod_epicloot_config_patchradius": "Полупречник мрље (м)",
+ "mod_epicloot_config_patchlifetime": "Трајање мрље (с)",
+ "mod_epicloot_config_patchtickinterval": "Интервал откуцаја мрље (с)",
+ "mod_epicloot_config_speedfloor": "Доња граница брзине",
+ "mod_epicloot_config_batlifetime": "Трајање слепог миша (с)",
+ "mod_epicloot_config_summoncountoffset": "Померај броја призваних",
+ "mod_epicloot_config_concurrentpersummon": "Макс. истовремено по призиву",
+ "mod_epicloot_config_maxreduction": "Макс. смањење",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% највећег здравља по вредности",
+ "mod_epicloot_config_minhealthreduction": "Мин. смањење здравља",
+ "mod_epicloot_config_carryweightpervalue": "Носивост по вредности",
+ "mod_epicloot_config_minresultingcarryweight": "Мин. носивост",
+ "mod_epicloot_config_weightthreshold": "Праг тежине",
+ "mod_epicloot_config_weightperbonus": "Тежина по бонусу",
+ "mod_epicloot_config_eitrcostperdamage": "Трошак еитра по штети",
+ "mod_epicloot_config_minskilllevel": "Мин. ниво вештине",
+ "mod_epicloot_config_levelweightexponent": "Експонент тежине нивоа",
+ "mod_epicloot_config_maxlevelupsperproc": "Макс. повећања нивоа по окидању",
+ "mod_epicloot_config_maxmultiplier": "Макс. множилац плена",
+ "mod_epicloot_config_maxunitsperentry": "Макс. јединица по ставци",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Макс. додатих јединица по убиству",
+ "mod_epicloot_config_rollbase": "Основа бацања",
+ "mod_epicloot_config_rollpervalue": "Бацање по вредности",
+ "mod_epicloot_config_rollcurve": "Крива бацања",
+ "mod_epicloot_config_floorfraction": "Удео доње границе",
+ "mod_epicloot_config_chitin": "Хитин",
+ "mod_epicloot_config_copperscrap": "Бакарни отпад",
+ "mod_epicloot_config_ironscrap": "Гвоздени отпад",
+ "mod_epicloot_config_runestoneepic": "Епски рунски камен",
+ "mod_epicloot_config_runestonelegendary": "Легендарни рунски камен",
"mod_epicloot_me_modifylowhealth_config_max": "Највећи праг ниског здравља",
"mod_epicloot_me_riches_config_coins": "Вредност новчића",
"mod_epicloot_me_riches_config_amber": "Вредност ћилибара",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(трајно)",
"mod_epicloot_legendary_brokkrgift": "Легендарни Броков дар",
"mod_epicloot_mythic_brokkrgift": "Митски Броков дар",
+ "mod_epicloot_ancient_brokkrgift": "Древни Броков дар",
"mod_epicloot_slotchisel_introduce": "Изузетно моћан артефакт за који се каже да у било који магични предмет уграђује утор за крхотине.",
"mod_epicloot_slotchisel_hint": "Превуци на магични предмет да додаш утор за крхотине",
"mod_epicloot_slotchisel_confirm_title": "Додати утор за крхотине?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Свако котрљање за измицање уклања {0} секунди са твојих отровних и горућих ефеката.",
"mod_epicloot_me_stormrider_display": "+{0} % брзине кретања у олујама",
"mod_epicloot_me_stormrider_desc": "+{0} % брзине кретања док бесни олуја.",
- "mod_epicloot_me_shockingcharge_display": "Шокантни набој",
- "mod_epicloot_me_shockingcharge_desc": "Твоји борбени поготци скупљају набоје. При пуном набоју следећи погодак ослобађа муњевити ударни талас унапред који наноси део штете твојих претходних погодака.",
- "mod_epicloot_me_forestsaid_display": "Помоћ шуме",
- "mod_epicloot_me_forestsaid_desc": "Када те погоде, избијају спутавајуће корење и имобилишу оближње непријатеље.",
- "mod_epicloot_me_corpserot_display": "Лешна трулеж",
- "mod_epicloot_me_corpserot_desc": "Непријатељи које убијеш могу да прсну у облак отрова, тровајући оближње противнике.",
- "mod_epicloot_me_icyretribution_display": "Ледена одмазда",
- "mod_epicloot_me_icyretribution_desc": "Када те погоде, детонираш ледену нову око себе. Време опоравка скалира се са реткошћу.",
+ "mod_epicloot_me_shockingcharge_display": "Шокантни набој: на сваких {0:0.#} погодака",
+ "mod_epicloot_me_shockingcharge_desc": "На сваких {0:0.#} борбених погодака следећи погодак ослобађа муњевити ударни талас унапред који наноси {1:0.#}% штете тих погодака. Виша реткост тражи мање погодака.",
+ "mod_epicloot_me_forestsaid_display": "Помоћ шуме: {0:0.#} м, {1:0.#} с одмора",
+ "mod_epicloot_me_forestsaid_desc": "Када те погоде, избија спутавајуће корење и имобилише непријатеље у кругу од {0:0.#} метара. Виша реткост досеже даље и пуни се за {1:0.#} секунди.",
+ "mod_epicloot_me_corpserot_display": "Лешна трулеж: {0:0.#} отрова",
+ "mod_epicloot_me_corpserot_desc": "Непријатељи које убијеш могу да прсну у облак отрова, наносећи {0:0.#} штете отровом оближњим противницима.",
+ "mod_epicloot_me_icyretribution_display": "Ледена одмазда: {0:0.#} мраза",
+ "mod_epicloot_me_icyretribution_desc": "Када те погоде, детонираш ледену нову око себе која наноси {0:0.#} штете мразом. Виша реткост удара јаче и брже се опоравља.",
"mod_epicloot_me_meteorsummoner_display": "Призивач метеора: {0}× штета поготка",
"mod_epicloot_me_meteorsummoner_desc": "Сваких {1} погодака оружјем твој следећи погодак призива ватрени метеор на мету који наноси {0}× штету тог поготка као ватру.",
- "mod_epicloot_me_everflow_display": "Вечни ток Краљице",
- "mod_epicloot_me_everflow_desc": "Убијање створења даје сложиви бонус који појачава регенерацију здравља, издржљивости и еитра.",
- "mod_epicloot_me_necroticfire_display": "Некротична ватра",
- "mod_epicloot_me_necroticfire_desc": "Уклања сву физичку штету са твог оружја и додаје тај цео износ и као ватрену и као отровну штету.",
+ "mod_epicloot_me_everflow_display": "Вечни ток Краљице: +{0:0.#}% регенерације по слоју",
+ "mod_epicloot_me_everflow_desc": "Убијање створења даје сложиви бонус који појачава регенерацију здравља, издржљивости и еитра за {0:0.#}% по слоју, до {1:0.#} слојева.",
+ "mod_epicloot_me_necroticfire_display": "Некротична ватра: +{0:0.#}% ватре и отрова",
+ "mod_epicloot_me_necroticfire_desc": "Уклања сву физичку штету са твог оружја и додаје {0:0.#}% од ње као ватрену штету и исто толико као отровну.",
"mod_epicloot_circlet": "обод",
"mod_epicloot_belt": "каиш",
"mod_epicloot_remains": "остаци",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendaran {0}",
"mod_epicloot_basicmythicnameformat": "Mitski {0}",
+ "mod_epicloot_basicancientnameformat": "Drevni {0}",
"mod_epicloot_rare_prefix1": "Čudan",
"mod_epicloot_rare_prefix2": "Retka",
"mod_epicloot_rare_prefix3": "Kriptična",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epik",
"mod_epicloot_Legendary": "Legendaran",
"mod_epicloot_Mythic": "Mitološki",
+ "mod_epicloot_Ancient": "Drevni",
"mod_epicloot_search_placeholder": "Претрага...",
"mod_epicloot_no_active_adventures": "Нема активних авантура",
"mod_epicloot_set_bonuses": "Бонуси сета",
diff --git a/EpicLoot/localizations/Slovak.json b/EpicLoot/localizations/Slovak.json
index aef24888a..6abb4c9a5 100644
--- a/EpicLoot/localizations/Slovak.json
+++ b/EpicLoot/localizations/Slovak.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Pazúrik",
"mod_epicloot_config_serpentscale": "Šupina morského hada",
"mod_epicloot_config_triplechance": "Šanca na trojitý úlovok",
- "mod_epicloot_config_secondspercent": "Sekúnd na 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekúnd na 1 %",
"mod_epicloot_config_procchance": "Šanca na spustenie",
"mod_epicloot_config_bonusrollsmin": "Min. bonusových hodov na korisť",
"mod_epicloot_config_bonusrollsmax": "Max. bonusových hodov na korisť",
+ "mod_epicloot_config_cooldown": "Nabíjanie (s)",
+ "mod_epicloot_config_basecooldown": "Základné nabíjanie (s)",
+ "mod_epicloot_config_cooldownperrarity": "Nabíjanie podľa vzácnosti (s)",
+ "mod_epicloot_config_cooldownpervalue": "Nabíjanie podľa hodnoty (s)",
+ "mod_epicloot_config_buffduration": "Trvanie (s)",
+ "mod_epicloot_config_tickinterval": "Interval tiku (s)",
+ "mod_epicloot_config_radius": "Polomer (m)",
+ "mod_epicloot_config_baseradius": "Základný polomer (m)",
+ "mod_epicloot_config_radiuspertier": "Polomer podľa hodnoty (m)",
+ "mod_epicloot_config_maxcharges": "Zásahov na nabitie",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Zásahov na nabitie (základ)",
+ "mod_epicloot_config_chargespervalue": "Ušetrených zásahov podľa hodnoty",
+ "mod_epicloot_config_mincharges": "Min. zásahov na nabitie",
+ "mod_epicloot_config_mincooldown": "Min. nabíjanie (s)",
+ "mod_epicloot_config_damagepertrigger": "Poškodenie na spustenie",
+ "mod_epicloot_config_eitrpertrigger": "Eitr na spustenie",
+ "mod_epicloot_config_damagemultiplier": "Násobiteľ poškodenia",
+ "mod_epicloot_config_damagefraction": "Podiel poškodenia",
+ "mod_epicloot_config_damagepervalue": "Poškodenie podľa hodnoty",
+ "mod_epicloot_config_frostpertier": "Mrazivé poškodenie podľa hodnoty",
+ "mod_epicloot_config_poisonpertier": "Jedovaté poškodenie podľa hodnoty",
+ "mod_epicloot_config_spawnheight": "Výška zrodu (m)",
+ "mod_epicloot_config_spawnradius": "Polomer zrodu (m)",
+ "mod_epicloot_config_spawninterval": "Interval zrodu (s)",
+ "mod_epicloot_config_mindistance": "Min. vzdialenosť vypustenia (m)",
+ "mod_epicloot_config_maxdistance": "Max. vzdialenosť vypustenia (m)",
+ "mod_epicloot_config_projectilespeed": "Rýchlosť projektilu (m/s)",
+ "mod_epicloot_config_explosionradius": "Polomer výbuchu (m)",
+ "mod_epicloot_config_conelength": "Dĺžka kužeľa (m)",
+ "mod_epicloot_config_conemaxwidth": "Šírka kužeľa (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Okno ignorovania výboja (s)",
+ "mod_epicloot_config_minmovespeed": "Min. rýchlosť pohybu",
+ "mod_epicloot_config_patchradius": "Polomer škvrny (m)",
+ "mod_epicloot_config_patchlifetime": "Trvanie škvrny (s)",
+ "mod_epicloot_config_patchtickinterval": "Interval tiku škvrny (s)",
+ "mod_epicloot_config_speedfloor": "Dolná hranica rýchlosti",
+ "mod_epicloot_config_batlifetime": "Trvanie netopiera (s)",
+ "mod_epicloot_config_summoncountoffset": "Posun počtu privolaných",
+ "mod_epicloot_config_concurrentpersummon": "Max. súčasne na privolanie",
+ "mod_epicloot_config_maxreduction": "Max. zníženie",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% max. zdravia podľa hodnoty",
+ "mod_epicloot_config_minhealthreduction": "Min. zníženie zdravia",
+ "mod_epicloot_config_carryweightpervalue": "Nosnosť podľa hodnoty",
+ "mod_epicloot_config_minresultingcarryweight": "Min. nosnosť",
+ "mod_epicloot_config_weightthreshold": "Prah hmotnosti",
+ "mod_epicloot_config_weightperbonus": "Hmotnosť na bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Náklady eitru na poškodenie",
+ "mod_epicloot_config_minskilllevel": "Min. úroveň zručnosti",
+ "mod_epicloot_config_levelweightexponent": "Exponent váhy úrovne",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. zvýšení úrovne na spustenie",
+ "mod_epicloot_config_maxmultiplier": "Max. násobiteľ koristi",
+ "mod_epicloot_config_maxunitsperentry": "Max. jednotiek na položku",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. pridaných jednotiek na zabitie",
+ "mod_epicloot_config_rollbase": "Základ hodu",
+ "mod_epicloot_config_rollpervalue": "Hod podľa hodnoty",
+ "mod_epicloot_config_rollcurve": "Krivka hodu",
+ "mod_epicloot_config_floorfraction": "Podiel dolnej hranice",
+ "mod_epicloot_config_chitin": "Chitín",
+ "mod_epicloot_config_copperscrap": "Medený šrot",
+ "mod_epicloot_config_ironscrap": "Železný šrot",
+ "mod_epicloot_config_runestoneepic": "Epický runový kameň",
+ "mod_epicloot_config_runestonelegendary": "Legendárny runový kameň",
"mod_epicloot_me_modifylowhealth_config_max": "Maximálny prah nízkeho zdravia",
"mod_epicloot_me_riches_config_coins": "Hodnota mincí",
"mod_epicloot_me_riches_config_amber": "Hodnota jantáru",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(trvalé)",
"mod_epicloot_legendary_brokkrgift": "Legendárny Brokkrov dar",
"mod_epicloot_mythic_brokkrgift": "Mystický Brokkrov dar",
+ "mod_epicloot_ancient_brokkrgift": "Staroveký Brokkrov dar",
"mod_epicloot_slotchisel_introduce": "Nesmierne mocný artefakt, o ktorom sa hovorí, že do ľubovoľného magického predmetu vsadí slot na črepy.",
"mod_epicloot_slotchisel_hint": "Potiahni na magický predmet a pridaj slot na črepy",
"mod_epicloot_slotchisel_confirm_title": "Pridať slot na črepy?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Každý úhybový kotúľ odoberie {0} sekúnd z tvojich jedových a horiacich efektov.",
"mod_epicloot_me_stormrider_display": "+{0} % rýchlosti pohybu v búrkach",
"mod_epicloot_me_stormrider_desc": "+{0} % rýchlosti pohybu, kým zúri búrka.",
- "mod_epicloot_me_shockingcharge_display": "Šokujúci náboj",
- "mod_epicloot_me_shockingcharge_desc": "Tvoje zásahy v boji hromadia náboje. Pri plnom náboji ďalší zásah vypustí dopredu smerujúcu bleskovú rázovú vlnu, ktorá spôsobí časť poškodenia tvojich predchádzajúcich zásahov.",
- "mod_epicloot_me_forestsaid_display": "Pomoc lesa",
- "mod_epicloot_me_forestsaid_desc": "Keď si zasiahnutý, vyrazia spútavajúce korene a znehybnia nepriateľov nablízku.",
- "mod_epicloot_me_corpserot_display": "Mŕtvolný rozklad",
- "mod_epicloot_me_corpserot_desc": "Nepriatelia, ktorých zabiješ, môžu prasknúť v oblak jedu a otráviť protivníkov nablízku.",
- "mod_epicloot_me_icyretribution_display": "Ľadová odplata",
- "mod_epicloot_me_icyretribution_desc": "Keď si zasiahnutý, odpáliš okolo seba mrazivú novu. Doba obnovy sa škáluje so vzácnosťou.",
+ "mod_epicloot_me_shockingcharge_display": "Šokujúci náboj: každých {0:0.#} zásahov",
+ "mod_epicloot_me_shockingcharge_desc": "Každých {0:0.#} zásahov v boji ďalší zásah vypustí dopredu smerujúcu bleskovú rázovú vlnu, ktorá spôsobí {1:0.#} % poškodenia týchto zásahov. Vyššia vzácnosť vyžaduje menej zásahov.",
+ "mod_epicloot_me_forestsaid_display": "Pomoc lesa: {0:0.#} m, {1:0.#} s nabíjania",
+ "mod_epicloot_me_forestsaid_desc": "Keď si zasiahnutý, vyrazia spútavajúce korene a znehybnia nepriateľov do {0:0.#} metrov. Vyššia vzácnosť dosiahne ďalej a nabije sa za {1:0.#} sekúnd.",
+ "mod_epicloot_me_corpserot_display": "Mŕtvolný rozklad: {0:0.#} jedu",
+ "mod_epicloot_me_corpserot_desc": "Nepriatelia, ktorých zabiješ, môžu prasknúť v oblak jedu a spôsobiť protivníkom nablízku {0:0.#} poškodenia jedom.",
+ "mod_epicloot_me_icyretribution_display": "Ľadová odplata: {0:0.#} mrazu",
+ "mod_epicloot_me_icyretribution_desc": "Keď si zasiahnutý, odpáliš okolo seba mrazivú novu, ktorá spôsobí {0:0.#} poškodenia mrazom. Vyššia vzácnosť udrie silnejšie a obnoví sa skôr.",
"mod_epicloot_me_meteorsummoner_display": "Privolávač meteorov: {0}× poškodenie zásahu",
"mod_epicloot_me_meteorsummoner_desc": "Každých {1} zásahov zbraňou zvolá tvoj ďalší zásah na cieľ ohnivý meteor, ktorý spôsobí {0}× poškodenie toho zásahu ako oheň.",
- "mod_epicloot_me_everflow_display": "Večný tok Kráľovnej",
- "mod_epicloot_me_everflow_desc": "Zabitie tvora udelí vrstviace sa posilnenie, ktoré zvyšuje regeneráciu zdravia, výdrže a eitru.",
- "mod_epicloot_me_necroticfire_display": "Nekrotický oheň",
- "mod_epicloot_me_necroticfire_desc": "Odoberie tvojej zbrani všetko fyzické poškodenie a pridá celú túto hodnotu ako ohnivé aj jedovaté poškodenie.",
+ "mod_epicloot_me_everflow_display": "Večný tok Kráľovnej: +{0:0.#} % regenerácie za vrstvu",
+ "mod_epicloot_me_everflow_desc": "Zabitie tvora udelí vrstviace sa posilnenie, ktoré zvyšuje regeneráciu zdravia, výdrže a eitru o {0:0.#} % za vrstvu, až do {1:0.#} vrstiev.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotický oheň: +{0:0.#} % ohňa a jedu",
+ "mod_epicloot_me_necroticfire_desc": "Odoberie tvojej zbrani všetko fyzické poškodenie a pridá {0:0.#} % z neho ako ohnivé poškodenie a rovnako toľko ako jedovaté.",
"mod_epicloot_circlet": "korunka",
"mod_epicloot_belt": "opasok",
"mod_epicloot_remains": "zvyšky",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendárny {0}",
"mod_epicloot_basicmythicnameformat": "Mystický {0}",
+ "mod_epicloot_basicancientnameformat": "Staroveký {0}",
"mod_epicloot_rare_prefix1": "Zvláštne",
"mod_epicloot_rare_prefix2": "Zriedkavé",
"mod_epicloot_rare_prefix3": "Tajomné",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "epický",
"mod_epicloot_Legendary": "Legendárny",
"mod_epicloot_Mythic": "Mystický",
+ "mod_epicloot_Ancient": "Staroveký",
"mod_epicloot_search_placeholder": "Hľadať...",
"mod_epicloot_no_active_adventures": "Žiadne aktívne dobrodružstvá",
"mod_epicloot_set_bonuses": "Bonusy setu",
diff --git a/EpicLoot/localizations/Spanish.json b/EpicLoot/localizations/Spanish.json
index 5992bc40c..1d42d1b6b 100644
--- a/EpicLoot/localizations/Spanish.json
+++ b/EpicLoot/localizations/Spanish.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Pedernal",
"mod_epicloot_config_serpentscale": "Escama de serpiente",
"mod_epicloot_config_triplechance": "Probabilidad de captura triple",
- "mod_epicloot_config_secondspercent": "Segundos por 1 %",
+ "mod_epicloot_config_secondsperpercent": "Segundos por 1 %",
"mod_epicloot_config_procchance": "Probabilidad de activación",
"mod_epicloot_config_bonusrollsmin": "Tiradas de botín extra mín.",
"mod_epicloot_config_bonusrollsmax": "Tiradas de botín extra máx.",
+ "mod_epicloot_config_cooldown": "Recarga (s)",
+ "mod_epicloot_config_basecooldown": "Recarga base (s)",
+ "mod_epicloot_config_cooldownperrarity": "Recarga por rareza (s)",
+ "mod_epicloot_config_cooldownpervalue": "Recarga por valor (s)",
+ "mod_epicloot_config_buffduration": "Duración (s)",
+ "mod_epicloot_config_tickinterval": "Intervalo de tic (s)",
+ "mod_epicloot_config_radius": "Radio (m)",
+ "mod_epicloot_config_baseradius": "Radio base (m)",
+ "mod_epicloot_config_radiuspertier": "Radio por valor (m)",
+ "mod_epicloot_config_maxcharges": "Golpes hasta cargar",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Golpes hasta cargar (base)",
+ "mod_epicloot_config_chargespervalue": "Golpes ahorrados por valor",
+ "mod_epicloot_config_mincharges": "Mín. de golpes hasta cargar",
+ "mod_epicloot_config_mincooldown": "Recarga mín. (s)",
+ "mod_epicloot_config_damagepertrigger": "Daño por activación",
+ "mod_epicloot_config_eitrpertrigger": "Eitr por activación",
+ "mod_epicloot_config_damagemultiplier": "Multiplicador de daño",
+ "mod_epicloot_config_damagefraction": "Fracción de daño",
+ "mod_epicloot_config_damagepervalue": "Daño por valor",
+ "mod_epicloot_config_frostpertier": "Daño de escarcha por valor",
+ "mod_epicloot_config_poisonpertier": "Daño de veneno por valor",
+ "mod_epicloot_config_spawnheight": "Altura de aparición (m)",
+ "mod_epicloot_config_spawnradius": "Radio de aparición (m)",
+ "mod_epicloot_config_spawninterval": "Intervalo de aparición (s)",
+ "mod_epicloot_config_mindistance": "Distancia mín. de lanzamiento (m)",
+ "mod_epicloot_config_maxdistance": "Distancia máx. de lanzamiento (m)",
+ "mod_epicloot_config_projectilespeed": "Velocidad del proyectil (m/s)",
+ "mod_epicloot_config_explosionradius": "Radio de explosión (m)",
+ "mod_epicloot_config_conelength": "Longitud del cono (m)",
+ "mod_epicloot_config_conemaxwidth": "Anchura del cono (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Ventana para ignorar la descarga (s)",
+ "mod_epicloot_config_minmovespeed": "Velocidad de movimiento mín.",
+ "mod_epicloot_config_patchradius": "Radio de la zona (m)",
+ "mod_epicloot_config_patchlifetime": "Duración de la zona (s)",
+ "mod_epicloot_config_patchtickinterval": "Intervalo de tic de la zona (s)",
+ "mod_epicloot_config_speedfloor": "Velocidad mínima",
+ "mod_epicloot_config_batlifetime": "Duración del murciélago (s)",
+ "mod_epicloot_config_summoncountoffset": "Desplazamiento del número de invocaciones",
+ "mod_epicloot_config_concurrentpersummon": "Máx. simultáneos por invocación",
+ "mod_epicloot_config_maxreduction": "Reducción máx.",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% de salud máx. por valor",
+ "mod_epicloot_config_minhealthreduction": "Reducción de salud mín.",
+ "mod_epicloot_config_carryweightpervalue": "Capacidad de carga por valor",
+ "mod_epicloot_config_minresultingcarryweight": "Capacidad de carga mín.",
+ "mod_epicloot_config_weightthreshold": "Umbral de peso",
+ "mod_epicloot_config_weightperbonus": "Peso por bonificación",
+ "mod_epicloot_config_eitrcostperdamage": "Coste de eitr por daño",
+ "mod_epicloot_config_minskilllevel": "Nivel de habilidad mín.",
+ "mod_epicloot_config_levelweightexponent": "Exponente de ponderación del nivel",
+ "mod_epicloot_config_maxlevelupsperproc": "Máx. de subidas de nivel por activación",
+ "mod_epicloot_config_maxmultiplier": "Multiplicador de botín máx.",
+ "mod_epicloot_config_maxunitsperentry": "Máx. de unidades por entrada",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Máx. de unidades añadidas por muerte",
+ "mod_epicloot_config_rollbase": "Base de la tirada",
+ "mod_epicloot_config_rollpervalue": "Tirada por valor",
+ "mod_epicloot_config_rollcurve": "Curva de la tirada",
+ "mod_epicloot_config_floorfraction": "Fracción mínima",
+ "mod_epicloot_config_chitin": "Quitina",
+ "mod_epicloot_config_copperscrap": "Chatarra de cobre",
+ "mod_epicloot_config_ironscrap": "Chatarra de hierro",
+ "mod_epicloot_config_runestoneepic": "Piedra rúnica épica",
+ "mod_epicloot_config_runestonelegendary": "Piedra rúnica legendaria",
"mod_epicloot_me_modifylowhealth_config_max": "Umbral máximo de salud baja",
"mod_epicloot_me_riches_config_coins": "Valor de las monedas",
"mod_epicloot_me_riches_config_amber": "Valor del ámbar",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanente)",
"mod_epicloot_legendary_brokkrgift": "Regalo de Brokkr legendario",
"mod_epicloot_mythic_brokkrgift": "Regalo de Brokkr mítico",
+ "mod_epicloot_ancient_brokkrgift": "Regalo de Brokkr antiguo",
"mod_epicloot_slotchisel_introduce": "Un artefacto extremadamente poderoso que, según se dice, incrusta una ranura de fragmento en cualquier objeto mágico.",
"mod_epicloot_slotchisel_hint": "Arrástralo sobre un objeto mágico para añadir una ranura de fragmento",
"mod_epicloot_slotchisel_confirm_title": "¿Añadir ranura de fragmento?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Cada voltereta de esquiva elimina {0} segundos de tus efectos de veneno y quemadura.",
"mod_epicloot_me_stormrider_display": "+{0} % de velocidad de movimiento en tormentas",
"mod_epicloot_me_stormrider_desc": "+{0} % de velocidad de movimiento mientras ruge una tormenta.",
- "mod_epicloot_me_shockingcharge_display": "Carga electrizante",
- "mod_epicloot_me_shockingcharge_desc": "Tus golpes en combate acumulan cargas. Al máximo, el siguiente golpe libera una onda de choque eléctrica hacia delante que inflige una parte del daño de tus golpes anteriores.",
- "mod_epicloot_me_forestsaid_display": "Ayuda del bosque",
- "mod_epicloot_me_forestsaid_desc": "Cuando te golpean, brotan raíces que inmovilizan a los enemigos cercanos.",
- "mod_epicloot_me_corpserot_display": "Putrefacción cadavérica",
- "mod_epicloot_me_corpserot_desc": "Los enemigos que matas pueden estallar en una nube de veneno, envenenando a los adversarios cercanos.",
- "mod_epicloot_me_icyretribution_display": "Represalia gélida",
- "mod_epicloot_me_icyretribution_desc": "Cuando te golpean, detonas una nova de escarcha a tu alrededor. El tiempo de reutilización escala con la rareza.",
+ "mod_epicloot_me_shockingcharge_display": "Carga electrizante: cada {0:0.#} golpes",
+ "mod_epicloot_me_shockingcharge_desc": "Cada {0:0.#} golpes en combate, el siguiente golpe libera una onda de choque eléctrica hacia delante que inflige el {1:0.#}% del daño de esos golpes. Una rareza mayor exige menos golpes.",
+ "mod_epicloot_me_forestsaid_display": "Ayuda del bosque: {0:0.#} m, {1:0.#} s de recarga",
+ "mod_epicloot_me_forestsaid_desc": "Cuando te golpean, brotan raíces que inmovilizan a los enemigos en {0:0.#} metros. Una rareza mayor llega más lejos y se recarga en {1:0.#} segundos.",
+ "mod_epicloot_me_corpserot_display": "Putrefacción cadavérica: {0:0.#} de veneno",
+ "mod_epicloot_me_corpserot_desc": "Los enemigos que matas pueden estallar en una nube de veneno, infligiendo {0:0.#} de daño de veneno a los adversarios cercanos.",
+ "mod_epicloot_me_icyretribution_display": "Represalia gélida: {0:0.#} de escarcha",
+ "mod_epicloot_me_icyretribution_desc": "Cuando te golpean, detonas una nova de escarcha a tu alrededor que inflige {0:0.#} de daño de escarcha. Una rareza mayor golpea más fuerte y se recarga antes.",
"mod_epicloot_me_meteorsummoner_display": "Invocador de meteoros: ×{0} del daño del golpe",
"mod_epicloot_me_meteorsummoner_desc": "Cada {1} golpes de arma, tu siguiente golpe hace caer un meteoro ardiente sobre el objetivo que inflige {0} veces el daño de ese golpe como fuego.",
- "mod_epicloot_me_everflow_display": "Flujo eterno de la Reina",
- "mod_epicloot_me_everflow_desc": "Matar a una criatura otorga una mejora acumulable que potencia tu regeneración de salud, resistencia y eitr.",
- "mod_epicloot_me_necroticfire_display": "Fuego necrótico",
- "mod_epicloot_me_necroticfire_desc": "Elimina todo el daño físico de tu arma y añade esa cantidad completa como daño de fuego y de veneno a la vez.",
+ "mod_epicloot_me_everflow_display": "Flujo eterno de la Reina: +{0:0.#}% de regeneración por acumulación",
+ "mod_epicloot_me_everflow_desc": "Matar a una criatura otorga una mejora acumulable que potencia tu regeneración de salud, resistencia y eitr en un {0:0.#}% por acumulación, hasta {1:0.#} acumulaciones.",
+ "mod_epicloot_me_necroticfire_display": "Fuego necrótico: +{0:0.#}% de fuego y veneno",
+ "mod_epicloot_me_necroticfire_desc": "Elimina todo el daño físico de tu arma y añade un {0:0.#}% de este como daño de fuego y otro tanto como daño de veneno.",
"mod_epicloot_circlet": "diadema",
"mod_epicloot_belt": "cinturón",
"mod_epicloot_remains": "restos",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendario {0}",
"mod_epicloot_basicmythicnameformat": "Mítico {0}",
+ "mod_epicloot_basicancientnameformat": "Antiguo {0}",
"mod_epicloot_rare_prefix1": "Extraño",
"mod_epicloot_rare_prefix2": "Raro",
"mod_epicloot_rare_prefix3": "Críptico",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Épico",
"mod_epicloot_Legendary": "Legendario",
"mod_epicloot_Mythic": "Mítico",
+ "mod_epicloot_Ancient": "Antiguo",
"mod_epicloot_search_placeholder": "Buscar...",
"mod_epicloot_no_active_adventures": "No hay aventuras activas",
"mod_epicloot_set_bonuses": "Bonos de conjunto",
diff --git a/EpicLoot/localizations/Swedish.json b/EpicLoot/localizations/Swedish.json
index acece216e..9a1bef212 100644
--- a/EpicLoot/localizations/Swedish.json
+++ b/EpicLoot/localizations/Swedish.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Flinta",
"mod_epicloot_config_serpentscale": "Ormfjäll",
"mod_epicloot_config_triplechance": "Chans till trippelfångst",
- "mod_epicloot_config_secondspercent": "Sekunder per 1 %",
+ "mod_epicloot_config_secondsperpercent": "Sekunder per 1 %",
"mod_epicloot_config_procchance": "Utlösningschans",
"mod_epicloot_config_bonusrollsmin": "Min. bonusslag för byte",
"mod_epicloot_config_bonusrollsmax": "Max. bonusslag för byte",
+ "mod_epicloot_config_cooldown": "Nedkylning (s)",
+ "mod_epicloot_config_basecooldown": "Basnedkylning (s)",
+ "mod_epicloot_config_cooldownperrarity": "Nedkylning per sällsynthet (s)",
+ "mod_epicloot_config_cooldownpervalue": "Nedkylning per värde (s)",
+ "mod_epicloot_config_buffduration": "Varaktighet (s)",
+ "mod_epicloot_config_tickinterval": "Tickintervall (s)",
+ "mod_epicloot_config_radius": "Radie (m)",
+ "mod_epicloot_config_baseradius": "Basradie (m)",
+ "mod_epicloot_config_radiuspertier": "Radie per värde (m)",
+ "mod_epicloot_config_maxcharges": "Träffar till uppladdning",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Träffar till uppladdning (bas)",
+ "mod_epicloot_config_chargespervalue": "Sparade träffar per värde",
+ "mod_epicloot_config_mincharges": "Min. träffar till uppladdning",
+ "mod_epicloot_config_mincooldown": "Min. nedkylning (s)",
+ "mod_epicloot_config_damagepertrigger": "Skada per utlösning",
+ "mod_epicloot_config_eitrpertrigger": "Eitr per utlösning",
+ "mod_epicloot_config_damagemultiplier": "Skademultiplikator",
+ "mod_epicloot_config_damagefraction": "Skadeandel",
+ "mod_epicloot_config_damagepervalue": "Skada per värde",
+ "mod_epicloot_config_frostpertier": "Frostskada per värde",
+ "mod_epicloot_config_poisonpertier": "Giftskada per värde",
+ "mod_epicloot_config_spawnheight": "Spawnhöjd (m)",
+ "mod_epicloot_config_spawnradius": "Spawnradie (m)",
+ "mod_epicloot_config_spawninterval": "Spawnintervall (s)",
+ "mod_epicloot_config_mindistance": "Min. utskjutningsavstånd (m)",
+ "mod_epicloot_config_maxdistance": "Max. utskjutningsavstånd (m)",
+ "mod_epicloot_config_projectilespeed": "Projektilhastighet (m/s)",
+ "mod_epicloot_config_explosionradius": "Explosionsradie (m)",
+ "mod_epicloot_config_conelength": "Konlängd (m)",
+ "mod_epicloot_config_conemaxwidth": "Konbredd (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Ignoreringsfönster för urladdning (s)",
+ "mod_epicloot_config_minmovespeed": "Min. rörelsehastighet",
+ "mod_epicloot_config_patchradius": "Fältradie (m)",
+ "mod_epicloot_config_patchlifetime": "Fältlivslängd (s)",
+ "mod_epicloot_config_patchtickinterval": "Fälttickintervall (s)",
+ "mod_epicloot_config_speedfloor": "Hastighetsgolv",
+ "mod_epicloot_config_batlifetime": "Fladdermusens livslängd (s)",
+ "mod_epicloot_config_summoncountoffset": "Förskjutning av kallat antal",
+ "mod_epicloot_config_concurrentpersummon": "Max. samtidiga per kallelse",
+ "mod_epicloot_config_maxreduction": "Max. minskning",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% av max. hälsa per värde",
+ "mod_epicloot_config_minhealthreduction": "Min. hälsominskning",
+ "mod_epicloot_config_carryweightpervalue": "Bärvikt per värde",
+ "mod_epicloot_config_minresultingcarryweight": "Min. bärvikt",
+ "mod_epicloot_config_weightthreshold": "Vikttröskel",
+ "mod_epicloot_config_weightperbonus": "Vikt per bonus",
+ "mod_epicloot_config_eitrcostperdamage": "Eitr-kostnad per skada",
+ "mod_epicloot_config_minskilllevel": "Min. färdighetsnivå",
+ "mod_epicloot_config_levelweightexponent": "Exponent för nivåviktning",
+ "mod_epicloot_config_maxlevelupsperproc": "Max. nivåhöjningar per utlösning",
+ "mod_epicloot_config_maxmultiplier": "Max. bytesmultiplikator",
+ "mod_epicloot_config_maxunitsperentry": "Max. enheter per post",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Max. tillagda enheter per dödande",
+ "mod_epicloot_config_rollbase": "Slagbas",
+ "mod_epicloot_config_rollpervalue": "Slag per värde",
+ "mod_epicloot_config_rollcurve": "Slagkurva",
+ "mod_epicloot_config_floorfraction": "Golvandel",
+ "mod_epicloot_config_chitin": "Kitin",
+ "mod_epicloot_config_copperscrap": "Kopparskrot",
+ "mod_epicloot_config_ironscrap": "Järnskrot",
+ "mod_epicloot_config_runestoneepic": "Episk runsten",
+ "mod_epicloot_config_runestonelegendary": "Legendarisk runsten",
"mod_epicloot_me_modifylowhealth_config_max": "Maxtröskel för låg hälsa",
"mod_epicloot_me_riches_config_coins": "Myntvärde",
"mod_epicloot_me_riches_config_amber": "Bärnstensvärde",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(permanent)",
"mod_epicloot_legendary_brokkrgift": "Legendarisk Brokkrs gåva",
"mod_epicloot_mythic_brokkrgift": "Mytisk Brokkrs gåva",
+ "mod_epicloot_ancient_brokkrgift": "Uråldrig Brokkrs gåva",
"mod_epicloot_slotchisel_introduce": "En ytterst mäktig artefakt som sägs bädda in ett skärvfack i vilket magiskt föremål som helst.",
"mod_epicloot_slotchisel_hint": "Dra den på ett magiskt föremål för att lägga till ett skärvfack",
"mod_epicloot_slotchisel_confirm_title": "Lägga till skärvfack?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Varje undanrullning tar bort {0} sekunder från dina gift- och brinneffekter.",
"mod_epicloot_me_stormrider_display": "+{0} % rörelsehastighet i stormar",
"mod_epicloot_me_stormrider_desc": "+{0} % rörelsehastighet medan en storm rasar.",
- "mod_epicloot_me_shockingcharge_display": "Chockande laddning",
- "mod_epicloot_me_shockingcharge_desc": "Dina träffar i strid bygger upp laddningar. Vid full laddning släpper nästa träff loss en framåtriktad blixtchockvåg som gör en del av dina tidigare träffars skada.",
- "mod_epicloot_me_forestsaid_display": "Skogens hjälp",
- "mod_epicloot_me_forestsaid_desc": "När du träffas bryter snärjande rötter fram och binder fiender i närheten.",
- "mod_epicloot_me_corpserot_display": "Likröta",
- "mod_epicloot_me_corpserot_desc": "Fiender du dödar kan brista i ett moln av gift och förgifta närliggande motståndare.",
- "mod_epicloot_me_icyretribution_display": "Isig vedergällning",
- "mod_epicloot_me_icyretribution_desc": "När du träffas utlöser du en frostnova omkring dig. Nedkylningen skalar med sällsyntheten.",
+ "mod_epicloot_me_shockingcharge_display": "Chockande laddning: var {0:0.#} träff",
+ "mod_epicloot_me_shockingcharge_desc": "Var {0:0.#} träff i strid släpper nästa träff loss en framåtriktad blixtchockvåg som gör {1:0.#}% av de träffarnas skada. Högre sällsynthet kräver färre träffar.",
+ "mod_epicloot_me_forestsaid_display": "Skogens hjälp: {0:0.#} m, {1:0.#} s nedkylning",
+ "mod_epicloot_me_forestsaid_desc": "När du träffas bryter snärjande rötter fram och binder fiender inom {0:0.#} meter. Högre sällsynthet når längre och laddas på {1:0.#} sekunder.",
+ "mod_epicloot_me_corpserot_display": "Likröta: {0:0.#} gift",
+ "mod_epicloot_me_corpserot_desc": "Fiender du dödar kan brista i ett moln av gift och ge närliggande motståndare {0:0.#} giftskada.",
+ "mod_epicloot_me_icyretribution_display": "Isig vedergällning: {0:0.#} frost",
+ "mod_epicloot_me_icyretribution_desc": "När du träffas utlöser du en frostnova omkring dig som gör {0:0.#} frostskada. Högre sällsynthet slår hårdare och laddas snabbare.",
"mod_epicloot_me_meteorsummoner_display": "Meteorbesvärjare: {0}× träffskada",
"mod_epicloot_me_meteorsummoner_desc": "Var {1} vapenträff kallar din nästa träff ned en brinnande meteor på målet som gör {0}× den träffens skada som eld.",
- "mod_epicloot_me_everflow_display": "Drottningens eviga flöde",
- "mod_epicloot_me_everflow_desc": "Att döda en varelse ger en staplingsbar effekt som ökar din hälso-, uthållighets- och eitrregenerering.",
- "mod_epicloot_me_necroticfire_display": "Nekrotisk eld",
- "mod_epicloot_me_necroticfire_desc": "Tar bort all fysisk skada från ditt vapen och lägger till hela beloppet som både eld- och giftskada.",
+ "mod_epicloot_me_everflow_display": "Drottningens eviga flöde: +{0:0.#}% regenerering per stack",
+ "mod_epicloot_me_everflow_desc": "Att döda en varelse ger en staplingsbar effekt som ökar din hälso-, uthållighets- och eitrregenerering med {0:0.#}% per stack, upp till {1:0.#} stackar.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotisk eld: +{0:0.#}% eld och gift",
+ "mod_epicloot_me_necroticfire_desc": "Tar bort all fysisk skada från ditt vapen och lägger till {0:0.#}% av den som eldskada och lika mycket som giftskada.",
"mod_epicloot_circlet": "cirkel",
"mod_epicloot_belt": "bälte",
"mod_epicloot_remains": "rester",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Legendarisk {0}",
"mod_epicloot_basicmythicnameformat": "Mytisk {0}",
+ "mod_epicloot_basicancientnameformat": "Uråldrig {0}",
"mod_epicloot_rare_prefix1": "Konstig",
"mod_epicloot_rare_prefix2": "Sällsynt",
"mod_epicloot_rare_prefix3": "Kryptisk",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Episk",
"mod_epicloot_Legendary": "Legendarisk",
"mod_epicloot_Mythic": "Mytisk",
+ "mod_epicloot_Ancient": "Uråldrig",
"mod_epicloot_search_placeholder": "Sök...",
"mod_epicloot_no_active_adventures": "Inga aktiva äventyr",
"mod_epicloot_set_bonuses": "Setbonusar",
diff --git a/EpicLoot/localizations/Thai.json b/EpicLoot/localizations/Thai.json
index cbf65f830..fdba71e2d 100644
--- a/EpicLoot/localizations/Thai.json
+++ b/EpicLoot/localizations/Thai.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "หินเหล็กไฟ",
"mod_epicloot_config_serpentscale": "เกล็ดงูทะเล",
"mod_epicloot_config_triplechance": "โอกาสได้ปลาสามตัว",
- "mod_epicloot_config_secondspercent": "วินาทีต่อ 1%",
+ "mod_epicloot_config_secondsperpercent": "วินาทีต่อ 1%",
"mod_epicloot_config_procchance": "โอกาสกระตุ้น",
"mod_epicloot_config_bonusrollsmin": "การสุ่มลูทเพิ่มขั้นต่ำ",
"mod_epicloot_config_bonusrollsmax": "การสุ่มลูทเพิ่มสูงสุด",
+ "mod_epicloot_config_cooldown": "คูลดาวน์ (วิ)",
+ "mod_epicloot_config_basecooldown": "คูลดาวน์พื้นฐาน (วิ)",
+ "mod_epicloot_config_cooldownperrarity": "คูลดาวน์ต่อความหายาก (วิ)",
+ "mod_epicloot_config_cooldownpervalue": "คูลดาวน์ต่อค่า (วิ)",
+ "mod_epicloot_config_buffduration": "ระยะเวลา (วิ)",
+ "mod_epicloot_config_tickinterval": "ช่วงติ๊ก (วิ)",
+ "mod_epicloot_config_radius": "รัศมี (ม.)",
+ "mod_epicloot_config_baseradius": "รัศมีพื้นฐาน (ม.)",
+ "mod_epicloot_config_radiuspertier": "รัศมีต่อค่า (ม.)",
+ "mod_epicloot_config_maxcharges": "จำนวนครั้งที่โจมตีจนชาร์จเต็ม",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "จำนวนครั้งที่โจมตีจนชาร์จเต็ม (พื้นฐาน)",
+ "mod_epicloot_config_chargespervalue": "ครั้งที่ประหยัดได้ต่อค่า",
+ "mod_epicloot_config_mincharges": "จำนวนครั้งขั้นต่ำจนชาร์จเต็ม",
+ "mod_epicloot_config_mincooldown": "คูลดาวน์ขั้นต่ำ (วิ)",
+ "mod_epicloot_config_damagepertrigger": "ความเสียหายต่อการกระตุ้น",
+ "mod_epicloot_config_eitrpertrigger": "เอียเทอร์ต่อการกระตุ้น",
+ "mod_epicloot_config_damagemultiplier": "ตัวคูณความเสียหาย",
+ "mod_epicloot_config_damagefraction": "สัดส่วนความเสียหาย",
+ "mod_epicloot_config_damagepervalue": "ความเสียหายต่อค่า",
+ "mod_epicloot_config_frostpertier": "ความเสียหายน้ำแข็งต่อค่า",
+ "mod_epicloot_config_poisonpertier": "ความเสียหายพิษต่อค่า",
+ "mod_epicloot_config_spawnheight": "ความสูงการเกิด (ม.)",
+ "mod_epicloot_config_spawnradius": "รัศมีการเกิด (ม.)",
+ "mod_epicloot_config_spawninterval": "ช่วงการเกิด (วิ)",
+ "mod_epicloot_config_mindistance": "ระยะยิงขั้นต่ำ (ม.)",
+ "mod_epicloot_config_maxdistance": "ระยะยิงสูงสุด (ม.)",
+ "mod_epicloot_config_projectilespeed": "ความเร็วกระสุน (ม./วิ)",
+ "mod_epicloot_config_explosionradius": "รัศมีการระเบิด (ม.)",
+ "mod_epicloot_config_conelength": "ความยาวกรวย (ม.)",
+ "mod_epicloot_config_conemaxwidth": "ความกว้างกรวย (ม.)",
+ "mod_epicloot_config_dischargeignorewindow": "ช่วงเพิกเฉยการปล่อยประจุ (วิ)",
+ "mod_epicloot_config_minmovespeed": "ความเร็วเคลื่อนที่ขั้นต่ำ",
+ "mod_epicloot_config_patchradius": "รัศมีพื้นที่ (ม.)",
+ "mod_epicloot_config_patchlifetime": "ระยะเวลาพื้นที่ (วิ)",
+ "mod_epicloot_config_patchtickinterval": "ช่วงติ๊กของพื้นที่ (วิ)",
+ "mod_epicloot_config_speedfloor": "ขีดล่างความเร็ว",
+ "mod_epicloot_config_batlifetime": "ระยะเวลาค้างคาว (วิ)",
+ "mod_epicloot_config_summoncountoffset": "ค่าชดเชยจำนวนการเรียก",
+ "mod_epicloot_config_concurrentpersummon": "จำนวนพร้อมกันสูงสุดต่อการเรียก",
+ "mod_epicloot_config_maxreduction": "การลดสูงสุด",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% พลังชีวิตสูงสุดต่อค่า",
+ "mod_epicloot_config_minhealthreduction": "การลดพลังชีวิตขั้นต่ำ",
+ "mod_epicloot_config_carryweightpervalue": "น้ำหนักบรรทุกต่อค่า",
+ "mod_epicloot_config_minresultingcarryweight": "น้ำหนักบรรทุกขั้นต่ำ",
+ "mod_epicloot_config_weightthreshold": "เกณฑ์น้ำหนัก",
+ "mod_epicloot_config_weightperbonus": "น้ำหนักต่อโบนัส",
+ "mod_epicloot_config_eitrcostperdamage": "เอียเทอร์ที่ใช้ต่อความเสียหาย",
+ "mod_epicloot_config_minskilllevel": "ระดับทักษะขั้นต่ำ",
+ "mod_epicloot_config_levelweightexponent": "เลขชี้กำลังถ่วงน้ำหนักระดับ",
+ "mod_epicloot_config_maxlevelupsperproc": "จำนวนเลเวลอัปสูงสุดต่อการกระตุ้น",
+ "mod_epicloot_config_maxmultiplier": "ตัวคูณลูทสูงสุด",
+ "mod_epicloot_config_maxunitsperentry": "จำนวนสูงสุดต่อรายการ",
+ "mod_epicloot_config_maxaddedunitsperdeath": "จำนวนที่เพิ่มสูงสุดต่อการสังหาร",
+ "mod_epicloot_config_rollbase": "ค่าพื้นฐานการสุ่ม",
+ "mod_epicloot_config_rollpervalue": "การสุ่มต่อค่า",
+ "mod_epicloot_config_rollcurve": "เส้นโค้งการสุ่ม",
+ "mod_epicloot_config_floorfraction": "สัดส่วนขีดล่าง",
+ "mod_epicloot_config_chitin": "ไคทิน",
+ "mod_epicloot_config_copperscrap": "เศษทองแดง",
+ "mod_epicloot_config_ironscrap": "เศษเหล็ก",
+ "mod_epicloot_config_runestoneepic": "หินรูนระดับมหากาพย์",
+ "mod_epicloot_config_runestonelegendary": "หินรูนระดับตำนาน",
"mod_epicloot_me_modifylowhealth_config_max": "เพดานเกณฑ์พลังชีวิตต่ำ",
"mod_epicloot_me_riches_config_coins": "มูลค่าเหรียญ",
"mod_epicloot_me_riches_config_amber": "มูลค่าอำพัน",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(ถาวร)",
"mod_epicloot_legendary_brokkrgift": "ของขวัญจากบร็อกก์ระดับตำนาน",
"mod_epicloot_mythic_brokkrgift": "ของขวัญจากบร็อกก์ระดับเทพนิยาย",
+ "mod_epicloot_ancient_brokkrgift": "ของขวัญจากบร็อกก์ระดับโบราณ",
"mod_epicloot_slotchisel_introduce": "วัตถุโบราณที่ทรงพลังอย่างยิ่ง ว่ากันว่าสามารถฝังช่องชาร์ดลงในไอเทมเวทมนตร์ใดก็ได้",
"mod_epicloot_slotchisel_hint": "ลากไปวางบนไอเทมเวทมนตร์เพื่อเพิ่มช่องชาร์ด",
"mod_epicloot_slotchisel_confirm_title": "เพิ่มช่องชาร์ดหรือไม่",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "การกลิ้งหลบแต่ละครั้งลบเอฟเฟกต์พิษและไฟไหม้ออกไป {0} วินาที",
"mod_epicloot_me_stormrider_display": "+{0}% ความเร็วเคลื่อนที่ในพายุ",
"mod_epicloot_me_stormrider_desc": "+{0}% ความเร็วเคลื่อนที่ขณะที่พายุกระหน่ำ",
- "mod_epicloot_me_shockingcharge_display": "ประจุช็อก",
- "mod_epicloot_me_shockingcharge_desc": "การโจมตีโดนในการต่อสู้จะสะสมประจุ เมื่อเต็ม การฟาดครั้งถัดไปจะปล่อยคลื่นช็อกสายฟ้าไปข้างหน้า สร้างความเสียหายส่วนหนึ่งของการโจมตีครั้งก่อน",
- "mod_epicloot_me_forestsaid_display": "ความช่วยเหลือของป่า",
- "mod_epicloot_me_forestsaid_desc": "เมื่อถูกโจมตี รากไม้ที่พันเกี่ยวจะผุดขึ้นมาจับศัตรูที่อยู่ใกล้",
- "mod_epicloot_me_corpserot_display": "ซากเน่า",
- "mod_epicloot_me_corpserot_desc": "ศัตรูที่ถูกสังหารอาจระเบิดเป็นกลุ่มควันพิษที่ทำให้ศัตรูใกล้เคียงติดพิษ",
- "mod_epicloot_me_icyretribution_display": "การชำระแค้นเยือกแข็ง",
- "mod_epicloot_me_icyretribution_desc": "จุดชนวนการระเบิดน้ำแข็งรอบตัวคุณเมื่อถูกโจมตี คูลดาวน์แปรผันตามความหายาก",
+ "mod_epicloot_me_shockingcharge_display": "ประจุช็อก: ทุก {0:0.#} ครั้งที่โจมตีโดน",
+ "mod_epicloot_me_shockingcharge_desc": "ทุก {0:0.#} ครั้งที่โจมตีโดนในการต่อสู้ การฟาดครั้งถัดไปจะปล่อยคลื่นช็อกสายฟ้าไปข้างหน้า สร้างความเสียหาย {1:0.#}% ของความเสียหายจากการโจมตีเหล่านั้น ความหายากที่สูงกว่าต้องการจำนวนครั้งน้อยลง",
+ "mod_epicloot_me_forestsaid_display": "ความช่วยเหลือของป่า: {0:0.#} ม. คูลดาวน์ {1:0.#} วิ",
+ "mod_epicloot_me_forestsaid_desc": "เมื่อถูกโจมตี รากไม้ที่พันเกี่ยวจะผุดขึ้นมาจับศัตรูในระยะ {0:0.#} เมตร ความหายากที่สูงกว่าจะเข้าถึงได้ไกลขึ้นและฟื้นตัวใน {1:0.#} วินาที",
+ "mod_epicloot_me_corpserot_display": "ซากเน่า: พิษ {0:0.#}",
+ "mod_epicloot_me_corpserot_desc": "ศัตรูที่ถูกสังหารอาจระเบิดเป็นกลุ่มควันพิษ สร้างความเสียหายพิษ {0:0.#} แก่ศัตรูใกล้เคียง",
+ "mod_epicloot_me_icyretribution_display": "การชำระแค้นเยือกแข็ง: น้ำแข็ง {0:0.#}",
+ "mod_epicloot_me_icyretribution_desc": "จุดชนวนการระเบิดน้ำแข็งรอบตัวคุณเมื่อถูกโจมตี สร้างความเสียหายน้ำแข็ง {0:0.#} ความหายากที่สูงกว่าจะแรงกว่าและฟื้นตัวเร็วกว่า",
"mod_epicloot_me_meteorsummoner_display": "ผู้เรียกอุกกาบาต: {0} เท่าของความเสียหายที่โจมตีโดน",
"mod_epicloot_me_meteorsummoner_desc": "ทุก {1} ครั้งที่โจมตีโดนด้วยอาวุธนี้ การฟาดครั้งถัดไปจะเรียกอุกกาบาตลุกโชนลงมาใส่เป้าหมาย สร้างความเสียหายไฟ {0} เท่าของการฟาดครั้งนั้น",
- "mod_epicloot_me_everflow_display": "การไหลนิรันดร์ของราชินี",
- "mod_epicloot_me_everflow_desc": "การสังหารสิ่งมีชีวิตให้บัฟที่ซ้อนชั้นได้ ซึ่งเพิ่มการฟื้นพลังชีวิต สตามินา และเอียเทอร์",
- "mod_epicloot_me_necroticfire_display": "เพลิงเน่าตาย",
- "mod_epicloot_me_necroticfire_desc": "ลบความเสียหายกายภาพทั้งหมดออกจากอาวุธ แล้วเพิ่มปริมาณเต็มนั้นเข้าเป็นทั้งความเสียหายไฟและความเสียหายพิษ",
+ "mod_epicloot_me_everflow_display": "การไหลนิรันดร์ของราชินี: ฟื้นฟู +{0:0.#}% ต่อชั้น",
+ "mod_epicloot_me_everflow_desc": "การสังหารสิ่งมีชีวิตให้บัฟที่ซ้อนชั้นได้ ซึ่งเพิ่มการฟื้นพลังชีวิต สตามินา และเอียเทอร์ {0:0.#}% ต่อชั้น สูงสุด {1:0.#} ชั้น",
+ "mod_epicloot_me_necroticfire_display": "เพลิงเน่าตาย: ไฟและพิษ +{0:0.#}%",
+ "mod_epicloot_me_necroticfire_desc": "ลบความเสียหายกายภาพทั้งหมดออกจากอาวุธ แล้วเพิ่ม {0:0.#}% ของค่านั้นเป็นความเสียหายไฟ และเท่ากันอีกครั้งเป็นความเสียหายพิษ",
"mod_epicloot_circlet": "วงกลม",
"mod_epicloot_belt": "เข็มขัด",
"mod_epicloot_remains": "สิ่งที่เหลือ",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "ตำแหน่งสุดยอด {0}",
"mod_epicloot_basicmythicnameformat": "เทพธิดา {0}",
+ "mod_epicloot_basicancientnameformat": "โบราณ {0}",
"mod_epicloot_rare_prefix1": "แปลก",
"mod_epicloot_rare_prefix2": "หายาก",
"mod_epicloot_rare_prefix3": "ลับ",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "มหัศจร",
"mod_epicloot_Legendary": "ตำนาน",
"mod_epicloot_Mythic": "ส่วนที่เป็นตำนาน",
+ "mod_epicloot_Ancient": "โบราณ",
"mod_epicloot_search_placeholder": "ค้นหา...",
"mod_epicloot_no_active_adventures": "ไม่มีการผจญภัยที่กำลังดำเนินอยู่",
"mod_epicloot_set_bonuses": "โบนัสชุด",
diff --git a/EpicLoot/localizations/Turkish.json b/EpicLoot/localizations/Turkish.json
index ea1fc39f4..b3735756c 100644
--- a/EpicLoot/localizations/Turkish.json
+++ b/EpicLoot/localizations/Turkish.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Çakmaktaşı",
"mod_epicloot_config_serpentscale": "Deniz yılanı pulu",
"mod_epicloot_config_triplechance": "Üçlü av şansı",
- "mod_epicloot_config_secondspercent": "%1 başına saniye",
+ "mod_epicloot_config_secondsperpercent": "%1 başına saniye",
"mod_epicloot_config_procchance": "Tetiklenme şansı",
"mod_epicloot_config_bonusrollsmin": "Asg. bonus ganimet atışı",
"mod_epicloot_config_bonusrollsmax": "Azm. bonus ganimet atışı",
+ "mod_epicloot_config_cooldown": "Bekleme (sn)",
+ "mod_epicloot_config_basecooldown": "Temel bekleme (sn)",
+ "mod_epicloot_config_cooldownperrarity": "Nadirlik başına bekleme (sn)",
+ "mod_epicloot_config_cooldownpervalue": "Değer başına bekleme (sn)",
+ "mod_epicloot_config_buffduration": "Süre (sn)",
+ "mod_epicloot_config_tickinterval": "Tik aralığı (sn)",
+ "mod_epicloot_config_radius": "Yarıçap (m)",
+ "mod_epicloot_config_baseradius": "Temel yarıçap (m)",
+ "mod_epicloot_config_radiuspertier": "Değer başına yarıçap (m)",
+ "mod_epicloot_config_maxcharges": "Şarj için vuruş",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Şarj için vuruş (temel)",
+ "mod_epicloot_config_chargespervalue": "Değer başına kazanılan vuruş",
+ "mod_epicloot_config_mincharges": "Şarj için asg. vuruş",
+ "mod_epicloot_config_mincooldown": "Asg. bekleme (sn)",
+ "mod_epicloot_config_damagepertrigger": "Tetiklenme başına hasar",
+ "mod_epicloot_config_eitrpertrigger": "Tetiklenme başına Eitr",
+ "mod_epicloot_config_damagemultiplier": "Hasar çarpanı",
+ "mod_epicloot_config_damagefraction": "Hasar oranı",
+ "mod_epicloot_config_damagepervalue": "Değer başına hasar",
+ "mod_epicloot_config_frostpertier": "Değer başına buz hasarı",
+ "mod_epicloot_config_poisonpertier": "Değer başına zehir hasarı",
+ "mod_epicloot_config_spawnheight": "Belirme yüksekliği (m)",
+ "mod_epicloot_config_spawnradius": "Belirme yarıçapı (m)",
+ "mod_epicloot_config_spawninterval": "Belirme aralığı (sn)",
+ "mod_epicloot_config_mindistance": "Asg. fırlatma mesafesi (m)",
+ "mod_epicloot_config_maxdistance": "Azami fırlatma mesafesi (m)",
+ "mod_epicloot_config_projectilespeed": "Mermi hızı (m/sn)",
+ "mod_epicloot_config_explosionradius": "Patlama yarıçapı (m)",
+ "mod_epicloot_config_conelength": "Koni uzunluğu (m)",
+ "mod_epicloot_config_conemaxwidth": "Koni genişliği (m)",
+ "mod_epicloot_config_dischargeignorewindow": "Deşarj yok sayma penceresi (sn)",
+ "mod_epicloot_config_minmovespeed": "Asg. hareket hızı",
+ "mod_epicloot_config_patchradius": "Alan yarıçapı (m)",
+ "mod_epicloot_config_patchlifetime": "Alan süresi (sn)",
+ "mod_epicloot_config_patchtickinterval": "Alan tik aralığı (sn)",
+ "mod_epicloot_config_speedfloor": "Hız alt sınırı",
+ "mod_epicloot_config_batlifetime": "Yarasa süresi (sn)",
+ "mod_epicloot_config_summoncountoffset": "Çağırma sayısı kaydırması",
+ "mod_epicloot_config_concurrentpersummon": "Çağırma başına azami eşzamanlı",
+ "mod_epicloot_config_maxreduction": "Azami azaltım",
+ "mod_epicloot_config_maxhealthpercentpervalue": "Değer başına azami sağlık %",
+ "mod_epicloot_config_minhealthreduction": "Asg. sağlık azaltımı",
+ "mod_epicloot_config_carryweightpervalue": "Değer başına taşıma kapasitesi",
+ "mod_epicloot_config_minresultingcarryweight": "Asg. taşıma kapasitesi",
+ "mod_epicloot_config_weightthreshold": "Ağırlık eşiği",
+ "mod_epicloot_config_weightperbonus": "Bonus başına ağırlık",
+ "mod_epicloot_config_eitrcostperdamage": "Hasar başına Eitr maliyeti",
+ "mod_epicloot_config_minskilllevel": "Asg. beceri seviyesi",
+ "mod_epicloot_config_levelweightexponent": "Seviye ağırlığı üssü",
+ "mod_epicloot_config_maxlevelupsperproc": "Tetiklenme başına azami seviye atlama",
+ "mod_epicloot_config_maxmultiplier": "Azami ganimet çarpanı",
+ "mod_epicloot_config_maxunitsperentry": "Girdi başına azami birim",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Öldürme başına azami eklenen birim",
+ "mod_epicloot_config_rollbase": "Atış tabanı",
+ "mod_epicloot_config_rollpervalue": "Değer başına atış",
+ "mod_epicloot_config_rollcurve": "Atış eğrisi",
+ "mod_epicloot_config_floorfraction": "Alt sınır oranı",
+ "mod_epicloot_config_chitin": "Çitin",
+ "mod_epicloot_config_copperscrap": "Bakır hurda",
+ "mod_epicloot_config_ironscrap": "Hurda demir",
+ "mod_epicloot_config_runestoneepic": "Epik run taşı",
+ "mod_epicloot_config_runestonelegendary": "Efsanevi run taşı",
"mod_epicloot_me_modifylowhealth_config_max": "Azami düşük sağlık eşiği",
"mod_epicloot_me_riches_config_coins": "Sikke değeri",
"mod_epicloot_me_riches_config_amber": "Kehribar değeri",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(kalıcı)",
"mod_epicloot_legendary_brokkrgift": "Efsanevi Brokkr'un Armağanı",
"mod_epicloot_mythic_brokkrgift": "Mitolojik Brokkr'un Armağanı",
+ "mod_epicloot_ancient_brokkrgift": "Kadim Brokkr'un Armağanı",
"mod_epicloot_slotchisel_introduce": "Herhangi bir sihirli eşyaya kıymık yuvası yerleştirdiği söylenen son derece güçlü bir kalıntı.",
"mod_epicloot_slotchisel_hint": "Kıymık yuvası eklemek için sihirli bir eşyanın üzerine sürükleyin",
"mod_epicloot_slotchisel_confirm_title": "Kıymık yuvası eklensin mi?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Her kaçınma taklası zehir ve yanma etkilerinden {0} saniye siler.",
"mod_epicloot_me_stormrider_display": "Fırtınalarda +%{0} hareket hızı",
"mod_epicloot_me_stormrider_desc": "Bir fırtına sürdüğü sürece +%{0} hareket hızı.",
- "mod_epicloot_me_shockingcharge_display": "Çarpıcı yük",
- "mod_epicloot_me_shockingcharge_desc": "Savaştaki vuruşların yük biriktirir. Yük dolduğunda sonraki vuruş, önceki vuruşlarının hasarının bir kısmını veren ileriye doğru bir yıldırım şok dalgası salar.",
- "mod_epicloot_me_forestsaid_display": "Ormanın yardımı",
- "mod_epicloot_me_forestsaid_desc": "Vurulduğunda, dolanan kökler fışkırır ve yakındaki düşmanları hareketsiz bırakır.",
- "mod_epicloot_me_corpserot_display": "Ceset çürümesi",
- "mod_epicloot_me_corpserot_desc": "Öldürdüğün düşmanlar bir zehir bulutu halinde patlayarak yakındaki rakipleri zehirleyebilir.",
- "mod_epicloot_me_icyretribution_display": "Buzlu misilleme",
- "mod_epicloot_me_icyretribution_desc": "Vurulduğunda çevrende bir buz novası patlatırsın. Bekleme süresi nadirlikle ölçeklenir.",
+ "mod_epicloot_me_shockingcharge_display": "Çarpıcı yük: Her {0:0.#} vuruşta",
+ "mod_epicloot_me_shockingcharge_desc": "Her {0:0.#} savaş vuruşunda sonraki vuruşun, o vuruşların verdiği hasarın %{1:0.#} kadarını veren ileriye doğru bir yıldırım şok dalgası salar. Daha nadir bir kıymık daha az vuruş gerektirir.",
+ "mod_epicloot_me_forestsaid_display": "Ormanın yardımı: {0:0.#}m, {1:0.#}sn bekleme",
+ "mod_epicloot_me_forestsaid_desc": "Vurulduğunda, dolanan kökler fışkırır ve {0:0.#} metre içindeki düşmanları hareketsiz bırakır. Daha nadir bir kıymık daha uzağa ulaşır ve {1:0.#} saniyede dolar.",
+ "mod_epicloot_me_corpserot_display": "Ceset çürümesi: {0:0.#} zehir",
+ "mod_epicloot_me_corpserot_desc": "Öldürdüğün düşmanlar bir zehir bulutu halinde patlayarak yakındaki rakiplere {0:0.#} zehir hasarı verebilir.",
+ "mod_epicloot_me_icyretribution_display": "Buzlu misilleme: {0:0.#} buz",
+ "mod_epicloot_me_icyretribution_desc": "Vurulduğunda çevrende {0:0.#} buz hasarı veren bir buz novası patlatırsın. Daha nadir bir kıymık daha sert vurur ve daha çabuk dolar.",
"mod_epicloot_me_meteorsummoner_display": "Meteor çağırıcı: {0}× vuruş hasarı",
"mod_epicloot_me_meteorsummoner_desc": "Her {1} silah vuruşunda, sonraki vuruşun hedefe alevli bir meteor indirir ve o vuruşun hasarının {0} katını ateş olarak verir.",
- "mod_epicloot_me_everflow_display": "Kraliçe'nin sonsuz akışı",
- "mod_epicloot_me_everflow_desc": "Bir yaratığı öldürmek, sağlık, dayanıklılık ve eitr yenilenmeni artıran yığılabilir bir güçlendirme verir.",
- "mod_epicloot_me_necroticfire_display": "Nekrotik ateş",
- "mod_epicloot_me_necroticfire_desc": "Silahındaki tüm fiziksel hasarı alır ve bu tam miktarı hem ateş hem de zehir hasarı olarak ekler.",
+ "mod_epicloot_me_everflow_display": "Kraliçe'nin sonsuz akışı: Yığın başına +%{0:0.#} yenilenme",
+ "mod_epicloot_me_everflow_desc": "Bir yaratığı öldürmek, sağlık, dayanıklılık ve eitr yenilenmeni yığın başına %{0:0.#} artıran, en fazla {1:0.#} yığına kadar birikebilen bir güçlendirme verir.",
+ "mod_epicloot_me_necroticfire_display": "Nekrotik ateş: +%{0:0.#} ateş ve zehir",
+ "mod_epicloot_me_necroticfire_desc": "Silahındaki tüm fiziksel hasarı alır ve bunun %{0:0.#} kadarını ateş hasarı, aynı miktarı da zehir hasarı olarak ekler.",
"mod_epicloot_circlet": "taç",
"mod_epicloot_belt": "kemer",
"mod_epicloot_remains": "kalıntılar",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Efsanevi {0}",
"mod_epicloot_basicmythicnameformat": "Mitik {0}",
+ "mod_epicloot_basicancientnameformat": "Kadim {0}",
"mod_epicloot_rare_prefix1": "Garip",
"mod_epicloot_rare_prefix2": "Nadir",
"mod_epicloot_rare_prefix3": "Gizemli",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Epik",
"mod_epicloot_Legendary": "Efsanevi",
"mod_epicloot_Mythic": "Mitolojik",
+ "mod_epicloot_Ancient": "Kadim",
"mod_epicloot_search_placeholder": "Ara...",
"mod_epicloot_no_active_adventures": "Aktif macera yok",
"mod_epicloot_set_bonuses": "Set Bonusları",
diff --git a/EpicLoot/localizations/Ukrainian.json b/EpicLoot/localizations/Ukrainian.json
index 889a4fc24..06b82f176 100644
--- a/EpicLoot/localizations/Ukrainian.json
+++ b/EpicLoot/localizations/Ukrainian.json
@@ -49,10 +49,72 @@
"mod_epicloot_config_flint": "Кремінь",
"mod_epicloot_config_serpentscale": "Луска морського змія",
"mod_epicloot_config_triplechance": "Шанс потрійного улову",
- "mod_epicloot_config_secondspercent": "Секунд на 1 %",
+ "mod_epicloot_config_secondsperpercent": "Секунд на 1 %",
"mod_epicloot_config_procchance": "Шанс спрацювання",
"mod_epicloot_config_bonusrollsmin": "Мін. бонусних кидків здобичі",
"mod_epicloot_config_bonusrollsmax": "Макс. бонусних кидків здобичі",
+ "mod_epicloot_config_cooldown": "Перезарядка (с)",
+ "mod_epicloot_config_basecooldown": "Базова перезарядка (с)",
+ "mod_epicloot_config_cooldownperrarity": "Перезарядка за рідкість (с)",
+ "mod_epicloot_config_cooldownpervalue": "Перезарядка за значення (с)",
+ "mod_epicloot_config_buffduration": "Тривалість (с)",
+ "mod_epicloot_config_tickinterval": "Інтервал тику (с)",
+ "mod_epicloot_config_radius": "Радіус (м)",
+ "mod_epicloot_config_baseradius": "Базовий радіус (м)",
+ "mod_epicloot_config_radiuspertier": "Радіус за значення (м)",
+ "mod_epicloot_config_maxcharges": "Ударів до заряду",
+ "mod_epicloot_me_shockingcharge_config_maxcharges": "Ударів до заряду (базово)",
+ "mod_epicloot_config_chargespervalue": "Заощаджено ударів за значення",
+ "mod_epicloot_config_mincharges": "Мін. ударів до заряду",
+ "mod_epicloot_config_mincooldown": "Мін. перезарядка (с)",
+ "mod_epicloot_config_damagepertrigger": "Шкода за спрацювання",
+ "mod_epicloot_config_eitrpertrigger": "Ейтр за спрацювання",
+ "mod_epicloot_config_damagemultiplier": "Множник шкоди",
+ "mod_epicloot_config_damagefraction": "Частка шкоди",
+ "mod_epicloot_config_damagepervalue": "Шкода за значення",
+ "mod_epicloot_config_frostpertier": "Шкода холодом за значення",
+ "mod_epicloot_config_poisonpertier": "Шкода отрутою за значення",
+ "mod_epicloot_config_spawnheight": "Висота появи (м)",
+ "mod_epicloot_config_spawnradius": "Радіус появи (м)",
+ "mod_epicloot_config_spawninterval": "Інтервал появи (с)",
+ "mod_epicloot_config_mindistance": "Мін. дальність запуску (м)",
+ "mod_epicloot_config_maxdistance": "Макс. дальність запуску (м)",
+ "mod_epicloot_config_projectilespeed": "Швидкість снаряда (м/с)",
+ "mod_epicloot_config_explosionradius": "Радіус вибуху (м)",
+ "mod_epicloot_config_conelength": "Довжина конуса (м)",
+ "mod_epicloot_config_conemaxwidth": "Ширина конуса (м)",
+ "mod_epicloot_config_dischargeignorewindow": "Вікно ігнорування розряду (с)",
+ "mod_epicloot_config_minmovespeed": "Мін. швидкість пересування",
+ "mod_epicloot_config_patchradius": "Радіус області (м)",
+ "mod_epicloot_config_patchlifetime": "Час життя області (с)",
+ "mod_epicloot_config_patchtickinterval": "Інтервал тику області (с)",
+ "mod_epicloot_config_speedfloor": "Нижня межа швидкості",
+ "mod_epicloot_config_batlifetime": "Час життя кажана (с)",
+ "mod_epicloot_config_summoncountoffset": "Зміщення кількості прикликаних",
+ "mod_epicloot_config_concurrentpersummon": "Макс. одночасно на прикликання",
+ "mod_epicloot_config_maxreduction": "Макс. зниження",
+ "mod_epicloot_config_maxhealthpercentpervalue": "% макс. здоров'я за значення",
+ "mod_epicloot_config_minhealthreduction": "Мін. зниження здоров'я",
+ "mod_epicloot_config_carryweightpervalue": "Вантажопідйомність за значення",
+ "mod_epicloot_config_minresultingcarryweight": "Мін. вантажопідйомність",
+ "mod_epicloot_config_weightthreshold": "Поріг ваги",
+ "mod_epicloot_config_weightperbonus": "Вага за бонус",
+ "mod_epicloot_config_eitrcostperdamage": "Витрати ейтру за одиницю шкоди",
+ "mod_epicloot_config_minskilllevel": "Мін. рівень навички",
+ "mod_epicloot_config_levelweightexponent": "Показник ваги рівня",
+ "mod_epicloot_config_maxlevelupsperproc": "Макс. підвищень рівня за спрацювання",
+ "mod_epicloot_config_maxmultiplier": "Макс. множник здобичі",
+ "mod_epicloot_config_maxunitsperentry": "Макс. одиниць на запис",
+ "mod_epicloot_config_maxaddedunitsperdeath": "Макс. доданих одиниць за вбивство",
+ "mod_epicloot_config_rollbase": "Основа кидка",
+ "mod_epicloot_config_rollpervalue": "Кидок за значення",
+ "mod_epicloot_config_rollcurve": "Крива кидка",
+ "mod_epicloot_config_floorfraction": "Частка нижньої межі",
+ "mod_epicloot_config_chitin": "Хітин",
+ "mod_epicloot_config_copperscrap": "Брухт міді",
+ "mod_epicloot_config_ironscrap": "Металобрухт",
+ "mod_epicloot_config_runestoneepic": "Епічний рунний камінь",
+ "mod_epicloot_config_runestonelegendary": "Легендарний рунний камінь",
"mod_epicloot_me_modifylowhealth_config_max": "Максимальний поріг низького здоров'я",
"mod_epicloot_me_riches_config_coins": "Цінність монет",
"mod_epicloot_me_riches_config_amber": "Цінність бурштину",
@@ -86,6 +148,7 @@
"mod_epicloot_socket_tip_permanent": "(назавжди)",
"mod_epicloot_legendary_brokkrgift": "Легендарний дар Брокка",
"mod_epicloot_mythic_brokkrgift": "Міфічний дар Брокка",
+ "mod_epicloot_ancient_brokkrgift": "Давній дар Брокка",
"mod_epicloot_slotchisel_introduce": "Надзвичайно могутній артефакт, який, як кажуть, вбудовує гніздо для уламків у будь-який магічний предмет.",
"mod_epicloot_slotchisel_hint": "Перетягніть на магічний предмет, щоб додати гніздо для уламків",
"mod_epicloot_slotchisel_confirm_title": "Додати гніздо для уламків?",
@@ -560,20 +623,20 @@
"mod_epicloot_me_rollcleanse_desc": "Кожен перекид ухилення знімає {0} секунд із твоїх ефектів отрути та горіння.",
"mod_epicloot_me_stormrider_display": "+{0} % до швидкості пересування в бурю",
"mod_epicloot_me_stormrider_desc": "+{0} % до швидкості пересування, поки вирує буря.",
- "mod_epicloot_me_shockingcharge_display": "Разючий заряд",
- "mod_epicloot_me_shockingcharge_desc": "Твої удари в бою накопичують заряди. При повному заряді наступний удар випускає вперед ударну хвилю блискавок, що завдає частини шкоди твоїх попередніх ударів.",
- "mod_epicloot_me_forestsaid_display": "Допомога лісу",
- "mod_epicloot_me_forestsaid_desc": "Коли тебе б'ють, із землі виривається обплутуюче коріння й знерухомлює ворогів поблизу.",
- "mod_epicloot_me_corpserot_display": "Трупна гниль",
- "mod_epicloot_me_corpserot_desc": "Убиті тобою вороги можуть лопнути хмарою отрути, труячи супротивників поблизу.",
- "mod_epicloot_me_icyretribution_display": "Крижана відплата",
- "mod_epicloot_me_icyretribution_desc": "Коли тебе б'ють, навколо тебе вибухає крижана нова. Перезарядка залежить від рідкісності.",
+ "mod_epicloot_me_shockingcharge_display": "Разючий заряд: кожні {0:0.#} ударів",
+ "mod_epicloot_me_shockingcharge_desc": "Кожні {0:0.#} ударів у бою наступний удар випускає вперед ударну хвилю блискавок, що завдає {1:0.#}% шкоди цих ударів. Вища рідкість потребує менше ударів.",
+ "mod_epicloot_me_forestsaid_display": "Допомога лісу: {0:0.#} м, перезарядка {1:0.#} с",
+ "mod_epicloot_me_forestsaid_desc": "Коли тебе б'ють, із землі виривається обплутуюче коріння й знерухомлює ворогів у радіусі {0:0.#} метрів. Вища рідкість сягає далі й перезаряджається за {1:0.#} секунд.",
+ "mod_epicloot_me_corpserot_display": "Трупна гниль: {0:0.#} отрути",
+ "mod_epicloot_me_corpserot_desc": "Убиті тобою вороги можуть лопнути хмарою отрути, завдаючи супротивникам поблизу {0:0.#} шкоди отрутою.",
+ "mod_epicloot_me_icyretribution_display": "Крижана відплата: {0:0.#} холоду",
+ "mod_epicloot_me_icyretribution_desc": "Коли тебе б'ють, навколо тебе вибухає крижана нова, що завдає {0:0.#} шкоди холодом. Вища рідкість б'є сильніше й перезаряджається швидше.",
"mod_epicloot_me_meteorsummoner_display": "Прикликач метеорів: {0}× шкоди удару",
"mod_epicloot_me_meteorsummoner_desc": "Кожні {1} ударів зброєю твій наступний удар обрушує на ціль вогняний метеор, що завдає {0}× шкоди того удару у вигляді вогню.",
- "mod_epicloot_me_everflow_display": "Вічний потік Королеви",
- "mod_epicloot_me_everflow_desc": "Убивство істоти дає складаний ефект, що підвищує відновлення здоров'я, витривалості та ейтру.",
- "mod_epicloot_me_necroticfire_display": "Некротичний вогонь",
- "mod_epicloot_me_necroticfire_desc": "Знімає з твоєї зброї всю фізичну шкоду й додає цю повну величину і як шкоду вогнем, і як шкоду отрутою.",
+ "mod_epicloot_me_everflow_display": "Вічний потік Королеви: +{0:0.#}% відновлення за стак",
+ "mod_epicloot_me_everflow_desc": "Убивство істоти дає складаний ефект, що підвищує відновлення здоров'я, витривалості та ейтру на {0:0.#}% за стак, до {1:0.#} стаків.",
+ "mod_epicloot_me_necroticfire_display": "Некротичний вогонь: +{0:0.#}% вогню та отрути",
+ "mod_epicloot_me_necroticfire_desc": "Знімає з твоєї зброї всю фізичну шкоду й додає {0:0.#}% від неї як шкоду вогнем і стільки ж як шкоду отрутою.",
"mod_epicloot_circlet": "обруч",
"mod_epicloot_belt": "пояс",
"mod_epicloot_remains": "залишки",
@@ -1763,6 +1826,7 @@
"mod_epicloot_epicnameformat": "{0} {1}",
"mod_epicloot_basiclegendarynameformat": "Легендарний {0}",
"mod_epicloot_basicmythicnameformat": "Міфічний {0}",
+ "mod_epicloot_basicancientnameformat": "Давній {0}",
"mod_epicloot_rare_prefix1": "Дивний",
"mod_epicloot_rare_prefix2": "Рідкісний",
"mod_epicloot_rare_prefix3": "Таємничий",
@@ -2021,6 +2085,7 @@
"mod_epicloot_Epic": "Епічний",
"mod_epicloot_Legendary": "Легендарний",
"mod_epicloot_Mythic": "Міфічний",
+ "mod_epicloot_Ancient": "Давній",
"mod_epicloot_search_placeholder": "Пошук...",
"mod_epicloot_no_active_adventures": "Немає активних пригод",
"mod_epicloot_set_bonuses": "Бонуси комплекту",
diff --git a/EpicLoot/src/Adventure/AbandonBountyDialog.cs b/EpicLoot/src/Adventure/AbandonBountyDialog.cs
index ae7894966..715308f5a 100644
--- a/EpicLoot/src/Adventure/AbandonBountyDialog.cs
+++ b/EpicLoot/src/Adventure/AbandonBountyDialog.cs
@@ -23,14 +23,14 @@ public void Awake()
NoButton.onClick.AddListener(OnNoButtonClicked);
}
- private void OnYesButtonClicked()
+ public void OnYesButtonClicked()
{
AdventureDataManager.Bounties.AbandonBounty(_bountyInfo);
MerchantPanel.OnAbandonBounty();
Close();
}
- private void OnNoButtonClicked()
+ public void OnNoButtonClicked()
{
Close();
}
diff --git a/EpicLoot/src/Adventure/AdventureSpawnWatchdog.cs b/EpicLoot/src/Adventure/AdventureSpawnWatchdog.cs
index 493e33ed3..73dc278af 100644
--- a/EpicLoot/src/Adventure/AdventureSpawnWatchdog.cs
+++ b/EpicLoot/src/Adventure/AdventureSpawnWatchdog.cs
@@ -1,6 +1,7 @@
using EpicLoot.Biomes;
using JetBrains.Annotations;
using System.Collections.Generic;
+using System.Linq;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -8,7 +9,8 @@ namespace EpicLoot.Adventure
{
///
/// Checks, from the buyer's side, that a bounty or treasure map actually appears once the buyer is
- /// standing at its map circle, and logs what it can see when it does not.
+ /// standing at its map circle, logs what it can see when it does not, and re-creates the spawner
+ /// when there is none left to place it.
///
/// The placement itself runs on whichever machine owns the spawner (see
/// ), which is often not the buyer's. That machine logs its own
@@ -17,8 +19,15 @@ namespace EpicLoot.Adventure
/// in, which is enough to tell "another machine is stuck placing it" from "nobody is placing it"
/// from "it was placed and is gone".
///
+ /// Only the last case is repaired. With no spawner and nothing it placed anywhere near the circle,
+ /// the bounty or map could never be finished, so the buyer drops a new spawner carrying the record
+ /// from their own save data - the biome, interval and bounty ID it was bought with, and a bounty's
+ /// progress so far. A spawner that still exists is left alone: another machine may be placing it.
+ ///
/// Everything here reads ZDOs the client already holds: the buyer is at the circle, so the server
- /// has synced the objects around it.
+ /// has synced the objects around it. Recovery additionally requires that the whole circle is inside
+ /// the area the server syncs to this client (), since an original
+ /// that is merely out of sight would otherwise be duplicated.
///
internal class AdventureSpawnWatchdog : MonoBehaviour
{
@@ -44,6 +53,12 @@ private sealed class Watch
/// Seen in place; nothing more to check this session.
public bool Resolved;
+
+ /// A replacement spawner was dropped; never a second one this session.
+ public bool Recovered;
+
+ /// Said once that recovery is waiting for the whole circle to be in sight.
+ public bool DeferralLogged;
}
private struct Snapshot
@@ -206,6 +221,91 @@ private void Check(string key, Vector3 centre, Vector3 playerPosition, float arr
EpicLoot.LogWarningForce($"{DescribeSpawn(bounty, map)} has not appeared {waited:0}s after you reached " +
$"its map circle at ({centre.x:0}, {centre.z:0}). {DescribeSpawner(snapshot)}");
}
+
+ // A report made on an earlier visit snapshots as soon as the player is back, before the ZDOs
+ // around the circle have had time to arrive, so recovery waits out the full delay on this
+ // visit whatever was reported before.
+ if (snapshot.Spawner != null || watch.Recovered || waited < AdventureSpawnController.OverdueSeconds)
+ {
+ return;
+ }
+
+ Vector3 spawnerPosition = bounty != null ? bounty.Position : map.Position;
+ if (!CanSeeWholeCircle(centre, spawnerPosition))
+ {
+ if (!watch.DeferralLogged)
+ {
+ watch.DeferralLogged = true;
+ EpicLoot.LogForce($"{DescribeSpawn(bounty, map)} will be re-created once its whole map circle is " +
+ "inside this client's simulation area, so that an original out of sight is not duplicated. " +
+ "Move towards the circle's centre.");
+ }
+
+ return;
+ }
+
+ watch.Recovered = true;
+ Recover(bounty, map, centre);
+ }
+
+ ///
+ /// Drops a new spawner at the circle, carrying the record from the buyer's save data. The
+ /// spawner copies the record into its ZDO, so the save data itself is untouched. It belongs to
+ /// this machine, and the buyer is standing right there, so it places straight away.
+ ///
+ private static void Recover(BountyInfo bounty, TreasureMapChestInfo map, Vector3 centre)
+ {
+ string remaining;
+ if (bounty != null)
+ {
+ AdventureSpawnController.CreateForBounty(bounty, centre);
+ int adds = bounty.Adds.Sum(x => x.Count);
+ remaining = bounty.Slain ? $"its {adds} remaining minion(s)" : $"the target and {adds} minion(s)";
+ }
+ else
+ {
+ AdventureSpawnController.CreateForTreasure(map, centre);
+ remaining = "a new chest";
+ }
+
+ EpicLoot.LogForce($"{DescribeSpawn(bounty, map)}: re-created its spawner at the map circle " +
+ $"({centre.x:0}, {centre.z:0}) from your save data. It will place {remaining}.");
+ }
+
+ ///
+ /// Whether this machine would hold the ZDOs of the original spawner and anything it placed, had
+ /// they still existed: every zone the circle touches, and the zone the original spawner was
+ /// dropped in. A client is only sent the objects in its near simulation area - the same area
+ /// places in - so on the lowest Simulation Distance a
+ /// player at the edge of the circle cannot see its far side. The server holds every ZDO.
+ ///
+ private static bool CanSeeWholeCircle(Vector3 centre, Vector3 spawnerPosition)
+ {
+ if (ZNet.instance.IsServer())
+ {
+ return true;
+ }
+
+ float radius = MinimapController.AreaRadius;
+ Vector2s min = ZoneSystem.GetZone(centre - new Vector3(radius, 0f, radius));
+ Vector2s max = ZoneSystem.GetZone(centre + new Vector3(radius, 0f, radius));
+ for (int y = min.y; y <= max.y; y++)
+ {
+ for (int x = min.x; x <= max.x; x++)
+ {
+ if (!IsZoneInSight(new Vector2s(x, y)))
+ {
+ return false;
+ }
+ }
+ }
+
+ return IsZoneInSight(ZoneSystem.GetZone(spawnerPosition));
+ }
+
+ private static bool IsZoneInSight(Vector2s zone)
+ {
+ return AdventureSpawnController.IsZoneInLocalNearArea(zone) && AdventureSpawnController.IsZoneInstantiated(zone);
}
///
diff --git a/EpicLoot/src/Adventure/BaseMerchantPanelListElement.cs b/EpicLoot/src/Adventure/BaseMerchantPanelListElement.cs
index ab065af30..f2f7a7060 100644
--- a/EpicLoot/src/Adventure/BaseMerchantPanelListElement.cs
+++ b/EpicLoot/src/Adventure/BaseMerchantPanelListElement.cs
@@ -1,19 +1,33 @@
using UnityEngine;
+using UnityEngine.UI;
namespace EpicLoot.Adventure
{
public interface IMerchantPanelListElement
{
void SetSelected(bool selected);
+ Button GetButton();
}
public class BaseMerchantPanelListElement : MonoBehaviour, IMerchantPanelListElement where T : class
{
public GameObject SelectedBackground;
+ private Button _button;
+
public void SetSelected(bool selected)
{
SelectedBackground.SetActive(selected);
}
+
+ public Button GetButton()
+ {
+ if (_button == null)
+ {
+ _button = GetComponent
- private static bool IsZoneInstantiated(Vector2s zone)
+ internal static bool IsZoneInstantiated(Vector2s zone)
{
return ZoneSystem.instance.IsZoneLoaded(zone) && !TryFindUninstantiatedObject(zone, out _);
}
diff --git a/EpicLoot/src/Adventure/bounties/AdventureSpawnSaveMarker.cs b/EpicLoot/src/Adventure/bounties/AdventureSpawnSaveMarker.cs
new file mode 100644
index 000000000..5c16b5696
--- /dev/null
+++ b/EpicLoot/src/Adventure/bounties/AdventureSpawnSaveMarker.cs
@@ -0,0 +1,164 @@
+using HarmonyLib;
+using JetBrains.Annotations;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace EpicLoot.Adventure
+{
+ ///
+ /// Makes the server write adventure objects a connected player created - a bounty or treasure map
+ /// spawner, and the creatures or chest it places - to disk on its next world save.
+ ///
+ /// Since the chunked save format, a world save rewrites only the chunks ZDOMan has marked dirty,
+ /// and only local writes mark one: ZDO.IncreaseDataRevision, ZDO.IncreaseOwnerRevision, and a
+ /// persistent ZDO moving between sectors. ZDOMan.RPC_ZDOData, which files the ZDOs a client sends,
+ /// does none of them - a new ZDO is added to its sector while Persistent is still false, and
+ /// Deserialize sets the flag and the data directly. Vanilla's ownership handoff, the usual source
+ /// of dirty chunks, only runs near players, and a spawner is dropped at a map circle far from
+ /// everyone. A server restart before anyone reached the circle lost the spawner, and the bounty or
+ /// map stayed in progress with nothing ever appearing there. What a client places is exposed the
+ /// same way until its chunk is marked by something else.
+ ///
+ /// The server's own spawners and placements are local writes and were never affected. The Valheim
+ /// Community Patch fixes this for every object ("Fix Unsaved Client Changes"); this covers the
+ /// adventure objects on servers without it, and marking the same chunk twice is harmless.
+ ///
+ internal static class AdventureSpawnSaveMarker
+ {
+ private const string PlacedRpc = "el.AdventureSpawnPlaced";
+
+ ///
+ /// How long the server waits for a placed object named by the RPC to arrive. The RPC is sent at
+ /// once and the object with the next ZDO sync, so it is normally a fraction of a second; an
+ /// object killed or destroyed before it synced never arrives at all.
+ ///
+ private const float PendingSeconds = 60f;
+
+ /// Server only: placed objects the RPC named before their ZDO arrived, and when.
+ private static readonly Dictionary Pending = new();
+ private static readonly List ExpiredScratch = new();
+
+ internal static void RegisterRPC(ZRoutedRpc routedRpc)
+ {
+ if (Common.Utils.IsServer())
+ {
+ routedRpc.Register(PlacedRpc, RPC_Placed);
+ }
+ }
+
+ ///
+ /// Marks the chunks of objects the spawner just placed for the next world save. Only the server
+ /// can mark one, so a client names the objects to the server instead.
+ ///
+ internal static void MarkPlaced(IReadOnlyList placed)
+ {
+ if (placed.Count == 0 || ZNet.instance == null || ZDOMan.instance == null)
+ {
+ return;
+ }
+
+ if (ZNet.instance.IsServer())
+ {
+ foreach (ZDO zdo in placed)
+ {
+ ZDOMan.instance.SetDirtySector(zdo);
+ }
+
+ return;
+ }
+
+ var pkg = new ZPackage();
+ pkg.Write(placed.Count);
+ foreach (ZDO zdo in placed)
+ {
+ pkg.Write(zdo.m_uid);
+ }
+
+ ZRoutedRpc.instance.InvokeRoutedRPC(PlacedRpc, pkg);
+ }
+
+ private static void RPC_Placed(long sender, ZPackage pkg)
+ {
+ if (ZDOMan.instance == null)
+ {
+ return;
+ }
+
+ PruneExpired();
+
+ int count = pkg.ReadInt();
+ int waiting = 0;
+ for (int i = 0; i < count; i++)
+ {
+ ZDOID id = pkg.ReadZDOID();
+ ZDO zdo = ZDOMan.instance.GetZDO(id);
+ if (zdo != null)
+ {
+ ZDOMan.instance.SetDirtySector(zdo);
+ }
+ else
+ {
+ // Usually the case: the RPC went out immediately, the objects wait for the next sync.
+ Pending[id] = Time.time;
+ waiting++;
+ }
+ }
+
+ EpicLoot.Log($"Marked {count - waiting} placed adventure object(s) for the next world save; " +
+ $"{waiting} will be marked when they arrive.");
+ }
+
+ private static void PruneExpired()
+ {
+ if (Pending.Count == 0)
+ {
+ return;
+ }
+
+ ExpiredScratch.Clear();
+ foreach (KeyValuePair entry in Pending)
+ {
+ if (Time.time - entry.Value > PendingSeconds)
+ {
+ ExpiredScratch.Add(entry.Key);
+ }
+ }
+
+ foreach (ZDOID id in ExpiredScratch)
+ {
+ Pending.Remove(id);
+ }
+
+ ExpiredScratch.Clear();
+ }
+
+ ///
+ /// ZDO.Deserialize's only caller is ZDOMan.RPC_ZDOData, so this sees every ZDO a peer sends:
+ /// spawners, and the placed objects the RPC is still waiting for.
+ ///
+ [HarmonyPatch(typeof(ZDO), nameof(ZDO.Deserialize))]
+ private static class ZDO_Deserialize_Patch
+ {
+ [UsedImplicitly]
+ private static void Postfix(ZDO __instance)
+ {
+ // Runs for every ZDO received, so the cheap tests come first. Pending is only ever
+ // filled on the server.
+ if (__instance.GetPrefab() != AdventureSpawnController.PrefabHash &&
+ (Pending.Count == 0 || !Pending.Remove(__instance.m_uid)))
+ {
+ return;
+ }
+
+ if (ZNet.instance == null || !ZNet.instance.IsServer() || ZDOMan.instance == null)
+ {
+ return;
+ }
+
+ // RPC_ZDOData sets the position before calling Deserialize, so this is the sector the
+ // object was filed in. SetDirtySector is the call a local write makes.
+ ZDOMan.instance.SetDirtySector(__instance);
+ }
+ }
+ }
+}
diff --git a/EpicLoot/src/Adventure/feature/Bounties.cs b/EpicLoot/src/Adventure/feature/Bounties.cs
index e5912f717..d3bb08770 100755
--- a/EpicLoot/src/Adventure/feature/Bounties.cs
+++ b/EpicLoot/src/Adventure/feature/Bounties.cs
@@ -12,7 +12,6 @@
using System.Linq;
using System.Text;
using UnityEngine;
-using Object = UnityEngine.Object;
using Random = System.Random;
namespace EpicLoot.Adventure.Feature
@@ -277,6 +276,7 @@ public IEnumerator AcceptBounty(Player player, BountyInfo bounty, Action().SetBounty(bounty);
- created_go.GetComponent().SetIsBounty();
+ // Pass the bounty data to the spawner, which saves it to its ZNetView
+ AdventureSpawnController.CreateForBounty(bounty, spawnPoint);
Minimap.instance.ShowPointOnMap(spawnPoint + offset);
}
@@ -359,6 +355,12 @@ private static void OnBountyTargetSlain(AdventureSaveData saveData, string bount
RemoveMinimapPin(bountyInfo);
}
+
+ // Persist on every credited kill, not just on completion: the add counts above are
+ // progress in their own right. This also covers the offline kill ledger, which replays
+ // through here after the server has already deleted the logs it sent -- an unwritten
+ // credit from that path cannot be recovered.
+ PlayerExtensions_Adventure.PersistLocalPlayerAdventureData();
}
public void ClaimBountyReward(BountyInfo bountyInfo)
@@ -376,6 +378,7 @@ public void ClaimBountyReward(BountyInfo bountyInfo)
}
bountyInfo.State = BountyState.Claimed;
+ player.SaveAdventureSaveData();
MessageHud.instance.ShowBiomeFoundMsg("$mod_epicloot_bounties_claimedmsg", true);
@@ -401,6 +404,7 @@ public void AbandonBounty(BountyInfo bountyInfo)
if (saveData != null && bountyInfo != null && saveData.BountyIsInProgress(bountyInfo.Interval, bountyInfo.ID))
{
saveData.AbandonedBounty(bountyInfo.ID);
+ PlayerExtensions_Adventure.PersistLocalPlayerAdventureData();
RemoveMinimapPin(bountyInfo);
}
}
diff --git a/EpicLoot/src/Adventure/feature/BountiesListPanel.cs b/EpicLoot/src/Adventure/feature/BountiesListPanel.cs
index 37c30f0ce..0b56f8ddd 100755
--- a/EpicLoot/src/Adventure/feature/BountiesListPanel.cs
+++ b/EpicLoot/src/Adventure/feature/BountiesListPanel.cs
@@ -12,6 +12,7 @@ public class AvailableBountiesListPanel : MerchantListPanel
public AvailableBountiesListPanel(MerchantPanel merchantPanel, BountyListElement elementPrefab)
: base(
merchantPanel.transform.Find("Bounties/AvailableBountiesPanel/ItemList") as RectTransform,
+ merchantPanel.transform.Find("Bounties/AvailableLabel"),
elementPrefab,
merchantPanel.transform.Find("Bounties/AcceptBountyButton").GetComponent(),
merchantPanel.transform.Find("Bounties/TimeLeft").GetComponent())
@@ -134,6 +135,7 @@ public class ClaimableBountiesListPanel : MerchantListPanel
public ClaimableBountiesListPanel(MerchantPanel merchantPanel, BountyListElement elementPrefab)
: base(
merchantPanel.transform.Find("Bounties/ClaimableBountiesPanel/ItemList") as RectTransform,
+ merchantPanel.transform.Find("Bounties/ClaimLabel"),
elementPrefab,
merchantPanel.transform.Find("Bounties/ClaimBountyButton").GetComponent(),
null)
@@ -146,6 +148,11 @@ public ClaimableBountiesListPanel(MerchantPanel merchantPanel, BountyListElement
AbandonButtonIcon = AbandonButton.transform.Find("Icon").GetComponent();
}
+ public override Button GetSecondaryButton()
+ {
+ return AbandonButton;
+ }
+
public override bool NeedsRefresh()
{
return _currentInterval != AdventureDataManager.Bounties.GetCurrentInterval();
diff --git a/EpicLoot/src/Adventure/feature/Gamble.cs b/EpicLoot/src/Adventure/feature/Gamble.cs
index b8cce5f0e..d7cec6f31 100755
--- a/EpicLoot/src/Adventure/feature/Gamble.cs
+++ b/EpicLoot/src/Adventure/feature/Gamble.cs
@@ -129,6 +129,7 @@ public void RecordGamblePurchase(Player player, SecretStashItemInfo itemInfo)
}
player.GetAdventureSaveData().PurchasedGamble(GetCurrentInterval(), GetGambleID(itemInfo));
+ player.SaveAdventureSaveData();
}
///
@@ -246,12 +247,23 @@ public ItemDrop.ItemData GenerateGambleItem(SecretStashItemInfo itemInfo)
}
};
+ // Restored in a finally: these are process-wide statics, and a roll that throws would
+ // otherwise leave gating disabled for every later drop in the session.
var previousDisabledState = LootRoller.CheatDisableGating;
- LootRoller.CheatDisableGating = true;
- LootRoller.CheatRollingItem = true;
- var loot = LootRoller.RollLootTable(lootTable, 1, "Gamble", Player.m_localPlayer.transform.position);
- LootRoller.CheatRollingItem = false;
- LootRoller.CheatDisableGating = previousDisabledState;
+ var previousRollingState = LootRoller.CheatRollingItem;
+ List loot;
+ try
+ {
+ LootRoller.CheatDisableGating = true;
+ LootRoller.CheatRollingItem = true;
+ loot = LootRoller.RollLootTable(lootTable, 1, "Gamble", Player.m_localPlayer.transform.position);
+ }
+ finally
+ {
+ LootRoller.CheatRollingItem = previousRollingState;
+ LootRoller.CheatDisableGating = previousDisabledState;
+ }
+
return loot.Count > 0 ? loot[0] : null;
}
}
diff --git a/EpicLoot/src/Adventure/feature/GambleListPanel.cs b/EpicLoot/src/Adventure/feature/GambleListPanel.cs
index 98214f509..c307173ac 100644
--- a/EpicLoot/src/Adventure/feature/GambleListPanel.cs
+++ b/EpicLoot/src/Adventure/feature/GambleListPanel.cs
@@ -12,6 +12,7 @@ public class GambleListPanel : MerchantListPanel
public GambleListPanel(MerchantPanel merchantPanel, BuyListElement elementPrefab)
: base(
merchantPanel.transform.Find("Gamble/Panel/ItemList") as RectTransform,
+ merchantPanel.transform.Find("Gamble/Title"),
elementPrefab,
merchantPanel.transform.Find("Gamble/GambleBuyButton").GetComponent(),
merchantPanel.transform.Find("Gamble/TimeLeft").GetComponent())
diff --git a/EpicLoot/src/Adventure/feature/MerchantListPanel.cs b/EpicLoot/src/Adventure/feature/MerchantListPanel.cs
index 5ef02a62e..d6f73e4c6 100644
--- a/EpicLoot/src/Adventure/feature/MerchantListPanel.cs
+++ b/EpicLoot/src/Adventure/feature/MerchantListPanel.cs
@@ -20,15 +20,38 @@ public interface IMerchantListPanel
void RefreshItems(Currencies currencies);
Button GetMainButton();
+
+ ///
+ /// The button the gamepad's secondary action presses, or null for a panel that has none.
+ ///
+ Button GetSecondaryButton();
+
+ /// The list's on-screen heading, which the gamepad hints hang off.
+ Transform GetHeader();
+
+ int GetItemCount();
+ int GetSelectedIndex();
+
+ ///
+ /// Selects the row at exactly as clicking it would, scrolls it into
+ /// view and hands it the EventSystem selection. Returns false when the index is off the end,
+ /// which is how the caller knows navigation ran out of list.
+ ///
+ bool SelectIndex(int index);
+
+ void ClearSelection();
}
public abstract class MerchantListPanel : IMerchantListPanel where T : Component, IMerchantPanelListElement
{
public RectTransform List;
+ public Transform Header;
public T ElementPrefab;
public Button MainButton;
public Text RefreshTime;
+ private readonly ScrollRectEnsureVisible _ensureVisible;
+
protected int _currentInterval = -1;
protected int _selectedItemIndex = -1;
@@ -65,16 +88,39 @@ protected void EndAction()
_actionInFlight = false;
}
- protected MerchantListPanel(RectTransform list, T elementPrefab, Button button, [CanBeNull] Text refreshTime)
+ protected MerchantListPanel(RectTransform list, Transform header, T elementPrefab, Button button,
+ [CanBeNull] Text refreshTime)
{
List = list;
+ Header = header;
+ _ensureVisible = list.GetComponentInParent();
ElementPrefab = elementPrefab;
ElementPrefab.gameObject.SetActive(false);
+ DisableNavigation(elementPrefab);
MainButton = button;
MainButton.onClick.AddListener(OnMainButtonClicked);
RefreshTime = refreshTime;
}
+ ///
+ /// Applied to the prefab every row is cloned from. A selected row is what gives the gamepad
+ /// its tooltip, but Unity's directional navigation acts on that same selection: left or right
+ /// walked it straight out of the focused list and into the next column's rows, which the panel
+ /// knows nothing about. The lists are only entered with the bumpers.
+ ///
+ private static void DisableNavigation(T elementPrefab)
+ {
+ Button button = elementPrefab.GetButton();
+ if (button == null)
+ {
+ return;
+ }
+
+ Navigation navigation = button.navigation;
+ navigation.mode = Navigation.Mode.None;
+ button.navigation = navigation;
+ }
+
public abstract bool NeedsRefresh();
public abstract void RefreshItems(Currencies currencies);
public abstract void UpdateRefreshTime();
@@ -109,6 +155,62 @@ public virtual Button GetMainButton()
return MainButton;
}
+ public virtual Button GetSecondaryButton()
+ {
+ return null;
+ }
+
+ public Transform GetHeader()
+ {
+ return Header;
+ }
+
+ public int GetItemCount()
+ {
+ return List.childCount;
+ }
+
+ public int GetSelectedIndex()
+ {
+ return _selectedItemIndex;
+ }
+
+ public bool SelectIndex(int index)
+ {
+ if (index < 0 || index >= List.childCount)
+ {
+ return false;
+ }
+
+ OnItemSelected(index);
+
+ T element = GetSelectedItem();
+ if (element == null)
+ {
+ return false;
+ }
+
+ if (_ensureVisible != null)
+ {
+ _ensureVisible.CenterOnItem((RectTransform)element.transform);
+ }
+
+ // The row's UITooltip watches the EventSystem selection when a gamepad is driving, so this
+ // is what puts the item's tooltip on screen for a player with no pointer to hover with.
+ Button button = element.GetButton();
+ if (button != null)
+ {
+ button.Select();
+ }
+
+ return true;
+ }
+
+ public void ClearSelection()
+ {
+ OnItemSelected(-1);
+ }
+
protected void UpdateRefreshTime(int seconds)
{
if (RefreshTime != null)
diff --git a/EpicLoot/src/Adventure/feature/SecretStashListPanel.cs b/EpicLoot/src/Adventure/feature/SecretStashListPanel.cs
index b2a725b07..e1df25313 100644
--- a/EpicLoot/src/Adventure/feature/SecretStashListPanel.cs
+++ b/EpicLoot/src/Adventure/feature/SecretStashListPanel.cs
@@ -12,6 +12,7 @@ public class SecretStashListPanel : MerchantListPanel
public SecretStashListPanel(MerchantPanel merchantPanel, BuyListElement elementPrefab)
: base(
merchantPanel.transform.Find("SecretStash/Panel/ItemList") as RectTransform,
+ merchantPanel.transform.Find("SecretStash/Title"),
elementPrefab,
merchantPanel.transform.Find("SecretStash/SecretStashBuyButton").GetComponent(),
merchantPanel.transform.Find("SecretStash/TimeLeft").GetComponent())
diff --git a/EpicLoot/src/Adventure/feature/TreasureMapListPanel.cs b/EpicLoot/src/Adventure/feature/TreasureMapListPanel.cs
index 6ada51b74..0d912cd10 100644
--- a/EpicLoot/src/Adventure/feature/TreasureMapListPanel.cs
+++ b/EpicLoot/src/Adventure/feature/TreasureMapListPanel.cs
@@ -13,6 +13,7 @@ class TreasureMapListPanel : MerchantListPanel
public TreasureMapListPanel(MerchantPanel merchantPanel, TreasureMapListElement elementPrefab)
: base(
merchantPanel.transform.Find("TreasureMap/Panel/ItemList") as RectTransform,
+ merchantPanel.transform.Find("TreasureMap/Title"),
elementPrefab,
merchantPanel.transform.Find("TreasureMap/TreasureMapBuyButton").GetComponent(),
merchantPanel.transform.Find("TreasureMap/TimeLeft").GetComponent())
diff --git a/EpicLoot/src/Adventure/feature/TreasureMaps.cs b/EpicLoot/src/Adventure/feature/TreasureMaps.cs
index f4c00722d..706be3897 100755
--- a/EpicLoot/src/Adventure/feature/TreasureMaps.cs
+++ b/EpicLoot/src/Adventure/feature/TreasureMaps.cs
@@ -1,11 +1,9 @@
using EpicLoot.Biomes;
-using Jotunn.Managers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
-using Object = UnityEngine.Object;
namespace EpicLoot.Adventure.Feature
{
@@ -108,11 +106,9 @@ private bool CreateTreasureSpawner(Heightmap.Biome biome, Vector3 spawnPoint, A
return false;
}
- Quaternion rotation = Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f);
- GameObject gameObject = PrefabManager.Instance.GetPrefab(AdventureSpawnController.PrefabName);
- GameObject created_go = Object.Instantiate(gameObject, spawnPoint, rotation);
- AdventureSpawnController asc = created_go.GetComponent();
- asc.SetTreasure(treasure_details);
+ PlayerExtensions_Adventure.PersistLocalPlayerAdventureData();
+
+ AdventureSpawnController.CreateForTreasure(treasure_details, spawnPoint);
Vector2 offset2 = UnityEngine.Random.insideUnitCircle *
(AdventureDataManager.Config.TreasureMap.MinimapAreaRadius * 0.8f);
diff --git a/EpicLoot/src/Compendium/MagicPages.cs b/EpicLoot/src/Compendium/MagicPages.cs
index ff15d7154..fb40fc93a 100644
--- a/EpicLoot/src/Compendium/MagicPages.cs
+++ b/EpicLoot/src/Compendium/MagicPages.cs
@@ -17,6 +17,9 @@ public class MagicPages : MonoBehaviour
public const int MEDIUM_FONT_SIZE = 20;
public const int FONT_SIZE = 18;
+ private const float GAMEPAD_SCROLL_SPEED = 1000f;
+ private const float GAMEPAD_STICK_DEADZONE = 0.5f;
+
public float MinWidth { get; private set; }
public float MinHeight { get; private set; }
@@ -75,6 +78,8 @@ public void Awake()
public void Update()
{
+ UpdateGamepadScroll();
+
// makes search field glow when focused
if (wasGlowing && !InSearchField())
{
@@ -88,6 +93,20 @@ public void Update()
}
}
+ private void UpdateGamepadScroll()
+ {
+ if (!MagicPagesTextArea.IsEnabled || !ZInput.IsGamepadActive())
+ {
+ return;
+ }
+
+ float rightStickAxis = ZInput.GetJoyRightStickY();
+ if (Mathf.Abs(rightStickAxis) > GAMEPAD_STICK_DEADZONE)
+ {
+ MagicPagesTextArea.ScrollBy(-rightStickAxis * GAMEPAD_SCROLL_SPEED * Time.unscaledDeltaTime);
+ }
+ }
+
public static bool InSearchField()
{
if (instance == null || instance.Search == null)
diff --git a/EpicLoot/src/Compendium/MagicTextList.cs b/EpicLoot/src/Compendium/MagicTextList.cs
index 270bad952..af3e9f069 100644
--- a/EpicLoot/src/Compendium/MagicTextList.cs
+++ b/EpicLoot/src/Compendium/MagicTextList.cs
@@ -58,6 +58,21 @@ public void ResizeOverlay()
private void SetOverlayHeight(float height) =>
_overlayRect.sizeDelta = new Vector2(_overlayRect.sizeDelta.x, Mathf.Max(height, MagicPages.instance.MinHeight));
+ public bool IsEnabled => _obj.activeSelf;
+
+ public void ScrollBy(float pixels)
+ {
+ RectTransform viewport = _scrollRect.viewport ? _scrollRect.viewport : _scrollRect.transform as RectTransform;
+ float scrollable = _scrollRect.content.rect.height - viewport!.rect.height;
+ if (scrollable <= 0f)
+ {
+ return;
+ }
+
+ _scrollRect.verticalNormalizedPosition =
+ Mathf.Clamp01(_scrollRect.verticalNormalizedPosition + (pixels / scrollable));
+ }
+
public void Clear()
{
foreach (MagicTextGroup group in Elements)
diff --git a/EpicLoot/src/Config/ELConfig.cs b/EpicLoot/src/Config/ELConfig.cs
index b1315e9cf..566535aa8 100755
--- a/EpicLoot/src/Config/ELConfig.cs
+++ b/EpicLoot/src/Config/ELConfig.cs
@@ -675,7 +675,7 @@ private static void RegisterSettingChangedHandlers() {
global::EpicLoot.TemperPanel.Instance?.ApplyConfiguredPosition();
TemperPanelPositionY.SettingChanged += (_, _) =>
global::EpicLoot.TemperPanel.Instance?.ApplyConfiguredPosition();
- _adventureModeEnabled.SettingChanged += (_, _) => MinimapController.RefreshAdventureToggleContainer();
+ _adventureModeEnabled.SettingChanged += (_, _) => MinimapController.RefreshAdventurePinFilters();
RemovePurchasedGambles.SettingChanged += (_, _) => {
// Only while the merchant is actually open -- rebuilding a hidden panel would
// instantiate every candidate ItemDrop for nothing, and it re-rolls on show anyway.
diff --git a/EpicLoot/src/Crafting/AugmentChoiceDialog.cs b/EpicLoot/src/Crafting/AugmentChoiceDialog.cs
index e74280e6e..c2b794a2c 100755
--- a/EpicLoot/src/Crafting/AugmentChoiceDialog.cs
+++ b/EpicLoot/src/Crafting/AugmentChoiceDialog.cs
@@ -133,7 +133,11 @@ public void Show(ItemDrop.ItemData fromItem, int effectIndex, Action();
- text.text = Localization.instance.Localize((index == 0 ? "($mod_epicloot_augment_keep) " : "") + MagicItem.GetEffectText(effect, rarity, true));
+ // Option 0 is the effect the item already carries (RollAugmentEffects puts it first), so
+ // it shows the item's own range; the new options were rolled from the rarity table.
+ var legendaryID = index == 0 ? magicItem.LegendaryID : null;
+ text.text = Localization.instance.Localize((index == 0 ? "($mod_epicloot_augment_keep) " : "") +
+ MagicItem.GetEffectText(effect, rarity, true, legendaryID));
text.color = rarityColor;
//if (EpicLoot.HasAuga)
diff --git a/EpicLoot/src/Crafting/AugmentHelper.cs b/EpicLoot/src/Crafting/AugmentHelper.cs
index 24a3608b4..1c66db63e 100755
--- a/EpicLoot/src/Crafting/AugmentHelper.cs
+++ b/EpicLoot/src/Crafting/AugmentHelper.cs
@@ -186,7 +186,9 @@ public static string GetAugmentSelectorText(MagicItem magicItem, int i,
string pip = EpicLoot.GetMagicEffectPip(magicItem.IsEffectAugmented(i));
bool free = EnchantCostsHelper.EffectIsDeprecated(augmentableEffects[i].EffectType);
- return $"{pip} {Localization.instance.Localize(MagicItem.GetEffectText(augmentableEffects[i], rarity, true))}" +
+ // The item's own legendary ID: these are effects already on the item, so a unique shows the
+ // range its legendary entry declares (if any) rather than the plain rarity table.
+ return $"{pip} {Localization.instance.Localize(MagicItem.GetEffectText(augmentableEffects[i], rarity, true, magicItem.LegendaryID))}" +
$"{(free ? " [*FREE]" : "")}";
}
diff --git a/EpicLoot/src/Crafting/TransferMagicalEffects.cs b/EpicLoot/src/Crafting/TransferMagicalEffects.cs
index 3091b838e..a372c8f4e 100755
--- a/EpicLoot/src/Crafting/TransferMagicalEffects.cs
+++ b/EpicLoot/src/Crafting/TransferMagicalEffects.cs
@@ -44,8 +44,8 @@ private static void TransferMagicToCraftedItem(Player player)
{
foreach (var effect in source.Effects)
{
- var def = MagicItemEffectDefinitions.Get(effect.EffectType);
- if (def == null)
+ // An effect with no registered definition is not carried over.
+ if (!MagicItemEffectDefinitions.TryGet(effect.EffectType, out var def))
continue;
// Same legality check the socket/rune system uses: allowed on this item type/rarity and
@@ -83,8 +83,8 @@ private static void TransferMagicToCraftedItem(Player player)
if (socket.ShardType == ShardType.None && socket.Effect != null)
{
// A runestone carries a fixed effect; keep it only if valid on the crafted item.
- var def = MagicItemEffectDefinitions.Get(socket.Effect.EffectType);
- if (def != null && def.Requirements.CheckRequirements(CraftedItem, newMagicItem, socket.Effect.EffectType, checklootroll: false))
+ if (MagicItemEffectDefinitions.TryGet(socket.Effect.EffectType, out var def) &&
+ def.Requirements.CheckRequirements(CraftedItem, newMagicItem, socket.Effect.EffectType, checklootroll: false))
carried.Effect = new MagicItemEffect(socket.Effect.EffectType, socket.Effect.EffectValue);
}
diff --git a/EpicLoot/src/CraftingV2/EnchantingUIController.cs b/EpicLoot/src/CraftingV2/EnchantingUIController.cs
index 0af63e89a..3cd5d6ebd 100644
--- a/EpicLoot/src/CraftingV2/EnchantingUIController.cs
+++ b/EpicLoot/src/CraftingV2/EnchantingUIController.cs
@@ -7,6 +7,7 @@
using EpicLoot.ShardStones;
using EpicLoot_UnityLib;
using Jotunn.Managers;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -718,7 +719,7 @@ private static List GetLootTablesForIdentifyStyle(IdentifyTypeConfig
internal static bool IsIdentifyGated(List items)
{
GatedItemTypeMode mode = EpicLoot.GetGatedItemTypeMode();
- if (mode == GatedItemTypeMode.Unlimited || mode == GatedItemTypeMode.PlayerMustKnowRecipe)
+ if (!GatedItemTypeHelper.UsesBossBiomeCap(mode))
{
return false;
}
@@ -745,47 +746,137 @@ internal static bool IsIdentifyGated(List items)
return false;
}
- internal static List LootRollSelectedItems(
- string filter, List> items, float powerModifier)
+ // Identifies the selected stacks and settles up for them. Every stack is rolled first, and unless
+ // each one rolled exactly as many items as it asked for, the whole identify is abandoned before
+ // anything is charged or removed (a short roll used to eat the inputs and keep the cost). Only
+ // then is the cost re-checked, the inputs removed, the cost paid and the results handed over --
+ // all for what was actually removed, should an input come up short. Returns null when nothing
+ // was identified.
+ internal static List LootRollSelectedItems(string filter,
+ List> items, float costModifier, float powerModifier)
{
IdentifyTypeConfig category = SelectLootIdentifyDetails(filter);
+ Player player = Player.m_localPlayer;
- if (category == null)
+ if (category == null || player == null || items == null || items.Count == 0)
{
- return new List();
+ return null;
}
- Player player = Player.m_localPlayer;
- List totalRolledItems = new List();
+ // The inputs as they are now, not as they were when the countdown started.
+ List heldItems = InventoryManagement.Instance.GetAllItems() ?? new List();
+ foreach (Tuple itemstack in items)
+ {
+ ItemDrop.ItemData input = itemstack.Item1;
+ if (input == null || itemstack.Item2 <= 0 || !input.IsUnidentified() ||
+ input.m_stack < itemstack.Item2 || !heldItems.Contains(input))
+ {
+ EpicLoot.LogWarning("Identify cancelled: a selected item is no longer available. Nothing was consumed.");
+ return null;
+ }
+ }
+
+ // Roll everything before touching the inventory. Rolled items only exist as ItemData until
+ // they are given, so an abandoned identify just drops them.
+ List> rolledPerStack = new List>();
foreach (Tuple itemstack in items)
{
Heightmap.Biome biome = EnchantHelper.GetBiomeFromUnidentifiedItem(itemstack.Item1);
List selectedLootTables = GetLootTablesForIdentifyStyle(category, biome);
List rolledItems = LootRoller.RollLootNoTableWithSpecifics(
player.transform.position, selectedLootTables, itemstack.Item2, itemstack.Item1.GetRarity(), true, 2, powerModifier);
- InventoryManagement.Instance.RemoveExactItem(itemstack.Item1, itemstack.Item2);
- totalRolledItems.AddRange(rolledItems);
- foreach (ItemDrop.ItemData item in rolledItems)
+ if (rolledItems.Count != itemstack.Item2)
{
- InventoryManagement.Instance.GiveItem(item);
+ EpicLoot.LogWarningForce($"Identify cancelled: {itemstack.Item1.m_shared.m_name} rolled " +
+ $"{rolledItems.Count} of {itemstack.Item2} items. Nothing was consumed. Check the identify " +
+ "loot lists in enchantcosts.json and the loot tables they name.");
+ return null;
}
+
+ rolledPerStack.Add(rolledItems);
}
-
+
+ List cost = GetIdentifyCostForCategory(filter, items, costModifier);
+ if (!EnchantingTableUIPanelBase.LocalPlayerCanAffordCost(cost))
+ {
+ EpicLoot.LogWarning("Identify cancelled: the cost can no longer be paid. Nothing was consumed.");
+ player.Message(MessageHud.MessageType.Center, "$msg_missingrequirement");
+ return null;
+ }
+
+ // Inputs before the cost: the inputs are the one step that can come up short (an inventory
+ // provider that no longer holds the stack), and a stack that yields nothing costs nothing.
+ List identifiedItems = new List();
+ List> removedStacks = new List>();
+ bool cameUpShort = false;
+ for (int index = 0; index < items.Count; index++)
+ {
+ Tuple itemstack = items[index];
+ int removed = InventoryManagement.Instance.RemoveExactItem(itemstack.Item1, itemstack.Item2);
+ if (removed < itemstack.Item2)
+ {
+ cameUpShort = true;
+ EpicLoot.LogWarningForce($"Identify: only {removed} of {itemstack.Item2} " +
+ $"{itemstack.Item1.m_shared.m_name} could be removed; identifying that many.");
+ }
+
+ if (removed > 0)
+ {
+ identifiedItems.AddRange(rolledPerStack[index].Take(removed));
+ removedStacks.Add(new Tuple(itemstack.Item1, removed));
+ }
+ }
+
+ if (identifiedItems.Count == 0)
+ {
+ return null;
+ }
+
+ if (!player.NoCostCheat())
+ {
+ // Charged for what was actually identified: the cost checked above unless a stack came
+ // up short, in which case it is worked out again for what was removed.
+ List charged = cameUpShort
+ ? GetIdentifyCostForCategory(filter, removedStacks, costModifier)
+ : cost;
+
+ foreach (InventoryItemListElement costElement in charged)
+ {
+ InventoryManagement.Instance.RemoveItem(costElement.GetItem());
+ }
+ }
+
+ foreach (ItemDrop.ItemData item in identifiedItems)
+ {
+ InventoryManagement.Instance.GiveItem(item);
+ }
+
EquipmentEffectCache.Reset(player);
- return totalRolledItems.Select(item => new InventoryItemListElement() { Item = item }).ToList();
+ return identifiedItems.Select(item => new InventoryItemListElement() { Item = item }).ToList();
}
- internal static List GetPotentialItemRollsByCategory(string filter, List itemsSelected)
+ // The items an identify could hand out, for the preview list. An entry progression gating would
+ // replace (or a denied prop) is left out, since the roll would never give that item; the
+ // substitute is not listed because picking it is a roll. hasLoot reports whether the loot lists
+ // name any item at all, gated or not, which is what decides whether identifying can work.
+ internal static List GetPotentialItemRollsByCategory(string filter,
+ List itemsSelected, out bool hasLoot)
{
IdentifyTypeConfig category = SelectLootIdentifyDetails(filter);
List resultItemNames = new List();
+ hasLoot = false;
if (category == null || Player.m_localPlayer == null)
{
return new List();
}
+ // The same mode the roll itself uses (LootRoller.RollLootNoTableWithSpecifics).
+ GatedItemTypeMode gatedMode = LootRoller.CheatDisableGating
+ ? GatedItemTypeMode.Unlimited
+ : EpicLoot.GetGatedItemTypeMode();
+
List biomesCovered = new List { };
foreach (ItemDrop.ItemData item in itemsSelected)
@@ -824,6 +915,12 @@ internal static List GetPotentialItemRollsByCategory(s
continue;
}
+ hasLoot = true;
+ if (!GatedItemTypeHelper.IsItemAvailableUngated(item, gatedMode))
+ {
+ continue;
+ }
+
result.Add(new InventoryItemListElement()
{
Item = founditem.GetComponent().m_itemData
@@ -1129,10 +1226,12 @@ internal static ItemDrop.ItemData BuildEnchantedRune(ItemDrop.ItemData selectedI
if (!float.IsNaN(powerModifier) && powerModifier < 999f && powerModifier > 0 && effect.EffectValue > 1)
{
runeEffect.EffectValue = effect.EffectValue * powerModifier;
- // Get() synthesizes a fallback for a missing definition (removed content mod,
- // deleted shard grid slot) -- the raw dictionary index threw KeyNotFound here.
- var valueDef = MagicItemEffectDefinitions.Get(effect.EffectType)?.ValuesPerRarity?
- .GetValueDefForRarity(selectedItem.GetRarity());
+ // TryGet, not the raw dictionary index (which threw KeyNotFound for a removed content
+ // mod or a deleted shard grid slot) and not Get(), whose stand-in would cap the rune to
+ // made-up numbers. The cap stays on the rarity table even for uniques, deliberately.
+ var valueDef = MagicItemEffectDefinitions.TryGet(effect.EffectType, out var effectDef)
+ ? effectDef.ValuesPerRarity?.GetValueDefForRarity(selectedItem.GetRarity())
+ : null;
if (valueDef != null)
{
float maxDefaultValue = valueDef.MaxValue;
@@ -1193,7 +1292,7 @@ internal static string GetRuneExtractWarningKey()
// Applies the item-reduction side effect after a rune has been extracted from it.
// reduceRarity: also drop the item one rarity tier and clamp remaining effect values down.
- // If no rolled effects remain, the item is reverted to a plain, non-magic item.
+ // If no rolled effects and no sockets remain, the item is reverted to a plain, non-magic item.
internal static void ReduceItemAfterRuneExtract(ItemDrop.ItemData item, int targetEnchant, bool reduceRarity)
{
MagicItem magicItem = item.GetMagicItem();
@@ -1202,20 +1301,11 @@ internal static void ReduceItemAfterRuneExtract(ItemDrop.ItemData item, int targ
return;
}
- magicItem.Effects.RemoveAt(targetEnchant);
+ ItemRarity previousRarity = magicItem.Rarity;
- // Drop stale augmented-effect bookkeeping so the augmented pip can't point at the wrong effect.
- magicItem.AugmentedEffectIndex = -1;
- magicItem.AugmentedEffectIndices?.Clear();
-
- // No rolled effects left -> revert to a plain, non-magic item. Dropping the component does
- // not write through SetMagicItem, so raise the change event by hand.
- if (magicItem.Effects.Count == 0)
- {
- item.Data().Remove();
- API.RaiseMagicItemChanged(item, API.ChangeReason.Rune);
- return;
- }
+ // Shifts the augmented/tempered markers of the later effects down with them, so each pip
+ // still sits on the effect it was earned by.
+ magicItem.RemoveEffectAt(targetEnchant);
if (reduceRarity && magicItem.Rarity > ItemRarity.Magic)
{
@@ -1225,7 +1315,9 @@ internal static void ReduceItemAfterRuneExtract(ItemDrop.ItemData item, int targ
foreach (MagicItemEffect effect in magicItem.Effects)
{
MagicItemEffectDefinition.ValueDef values =
- MagicItemEffectDefinitions.Get(effect.EffectType)?.GetValuesForRarity(magicItem.Rarity);
+ MagicItemEffectDefinitions.TryGet(effect.EffectType, out MagicItemEffectDefinition effectDef)
+ ? effectDef.GetValuesForRarity(magicItem.Rarity)
+ : null;
if (values != null && effect.EffectValue > values.MaxValue)
{
effect.EffectValue = values.MaxValue;
@@ -1233,54 +1325,113 @@ internal static void ReduceItemAfterRuneExtract(ItemDrop.ItemData item, int targ
}
}
+ // No rolled effects and no sockets left -> revert to a plain, non-magic item. Dropping the
+ // component does not write through SetMagicItem, so raise the change event by hand. An item
+ // with sockets stays magic with zero effects (the same state TransferMagicalEffects creates),
+ // since dropping the component would destroy the socket capacity and everything socketed.
+ if (magicItem.Effects.Count == 0 && magicItem.SocketCount <= 0 && magicItem.Sockets.Count == 0)
+ {
+ item.Data().Remove();
+ API.RaiseMagicItemChanged(item, API.ChangeReason.Rune);
+ return;
+ }
+
+ // Generated names are built from the rarity, and a Rare's from its first two effects; a
+ // unique keeps the name its legendary entry gave it.
+ bool nameFromEffects = magicItem.Rarity == ItemRarity.Rare && targetEnchant < 2;
+ if (string.IsNullOrEmpty(magicItem.LegendaryID) && (magicItem.Rarity != previousRarity || nameFromEffects))
+ {
+ magicItem.DisplayName = MagicItemNames.GetNameForItem(item, magicItem);
+ }
+
API.WithChangeReason(API.ChangeReason.Rune, () => item.SaveMagicItem(magicItem));
}
- internal static GameObject RuneEnhanceItemAndReturnSuccess(ItemDrop.ItemData item, ItemDrop.ItemData rune, int enchantment)
+ // Whether the rune tab may extract or overwrite the effect at this index: it has to exist and
+ // have a registered definition that allows it. An unknown effect is never runified.
+ internal static bool CanRunifyEffect(MagicItem magicItem, int index)
{
- List runeEffects = rune.GetMagicItem().Effects;
+ return magicItem != null && index >= 0 && index < magicItem.Effects.Count &&
+ MagicItemEffectDefinitions.TryGet(magicItem.Effects[index].EffectType, out MagicItemEffectDefinition effectDef) &&
+ effectDef.CanBeRunified;
+ }
- if (runeEffects.Count > 1)
+ // Works out what etching the rune onto the item produces, on a copy: the item itself is not
+ // touched, so the caller can take the rune and the cost before committing the result with
+ // ApplyRuneEtch -- and nothing is lost if either of those fails. Returns null when the etch
+ // cannot be made (no magic data, no rune effects, target index out of range).
+ internal static MagicItem BuildRuneEtchResult(ItemDrop.ItemData item, ItemDrop.ItemData rune, int enchantment)
+ {
+ MagicItem source = item?.GetMagicItem();
+ List runeEffects = rune?.GetMagicItem()?.Effects;
+ if (source == null || runeEffects == null || runeEffects.Count == 0 ||
+ enchantment < 0 || enchantment >= source.Effects.Count)
{
- foreach (MagicItemEffect effect in runeEffects)
+ return null;
+ }
+
+ // The same JSON round trip MagicItemComponent does on every load, so the copy is exactly
+ // what the item would carry.
+ MagicItem result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source));
+
+ for (int runeIndex = 0; runeIndex < runeEffects.Count; runeIndex++)
+ {
+ // Copied, never shared: the rune's effect objects stay with the rune (and the rest of
+ // its stack), and a later edit to one must not reach into the other.
+ MagicItemEffect effect = new MagicItemEffect(runeEffects[runeIndex].EffectType,
+ runeEffects[runeIndex].EffectValue);
+
+ // The first rune effect replaces the target enchantment
+ if (runeIndex == 0)
{
- // Replace the target enchantment
- if (runeEffects.IndexOf(effect) == 0)
- {
- item.GetMagicItem().Effects[enchantment] = effect;
- continue;
- }
+ SetEtchedEffect(result, enchantment, effect);
+ continue;
+ }
- // Skip or replace existing effects with the same effect type
- if (item.GetMagicItem().Effects.Any(x => x.EffectType == effect.EffectType))
+ // Skip or replace existing effects with the same effect type
+ if (result.Effects.Any(x => x.EffectType == effect.EffectType))
+ {
+ // If the item already has this effect, but with a lower value, replace it.
+ // Same-TYPE comparison: the old test matched any effect on the item with a
+ // smaller value, so a weaker rune could overwrite a stronger existing
+ // effect because some unrelated effect happened to roll low.
+ int index = result.Effects.FindIndex(x =>
+ x.EffectType == effect.EffectType && x.EffectValue < effect.EffectValue);
+ if (index >= 0)
{
- // If the item already has this effect, but with a lower value, replace it.
- // Same-TYPE comparison: the old test matched any effect on the item with a
- // smaller value, so a weaker rune could overwrite a stronger existing
- // effect because some unrelated effect happened to roll low.
- if (item.GetMagicItem().Effects.Any(x =>
- x.EffectType == effect.EffectType && x.EffectValue < effect.EffectValue))
- {
- int index = item.GetMagicItem().Effects.FindIndex(x => x.EffectType == effect.EffectType);
- item.GetMagicItem().Effects[index] = effect;
- }
-
- // If the item already has this effect, skip it
- continue;
+ SetEtchedEffect(result, index, effect);
}
- // Add additional effects
- item.GetMagicItem().Effects.Add(effect);
+ // If the item already has this effect, skip it
+ continue;
}
- }
- else
- {
- item.GetMagicItem().Effects[enchantment] = rune.GetMagicItem().Effects[0];
+
+ // Add additional effects
+ result.Effects.Add(effect);
}
- MagicItem magicItem = item.GetMagicItem();
- API.WithChangeReason(API.ChangeReason.Rune, () => item.SaveMagicItem(magicItem));
+ return result;
+ }
+
+ // An etch overwrites the whole effect in that slot, so a tempered marker earned by the value
+ // that was there goes with it.
+ private static void SetEtchedEffect(MagicItem magicItem, int index, MagicItemEffect effect)
+ {
+ magicItem.Effects[index] = effect;
+ magicItem.TemperedEffectIndices.Remove(index);
+ }
+ // Commits a result from BuildRuneEtchResult to the item.
+ internal static void ApplyRuneEtch(ItemDrop.ItemData item, MagicItem etchedMagicItem)
+ {
+ API.WithChangeReason(API.ChangeReason.Rune, () => item.SaveMagicItem(etchedMagicItem));
+
+ Game.instance.GetPlayerProfile().IncrementStat(PlayerStatType.Crafts);
+ Gogan.LogEvent("Game", "RuneEnhanced", item.m_shared.m_name, 1);
+ }
+
+ internal static GameObject ShowRuneEtchSuccessDialog(ItemDrop.ItemData item)
+ {
CraftSuccessDialog successDialog;
//if (EpicLoot.HasAuga)
//{
@@ -1317,9 +1468,6 @@ internal static GameObject RuneEnhanceItemAndReturnSuccess(ItemDrop.ItemData ite
}
}
- Game.instance.GetPlayerProfile().IncrementStat(PlayerStatType.Crafts);
- Gogan.LogEvent("Game", "RuneEnhanced", item.m_shared.m_name, 1);
-
return successDialog.gameObject;
}
@@ -1335,13 +1483,11 @@ internal static List> GetEnchantmentEffects(ItemDrop.ItemDat
for (int index = 0; index < augmentableEffects.Count; index++)
{
MagicItemEffect augmentableEffect = augmentableEffects[index];
- MagicItemEffectDefinition effectDef = MagicItemEffectDefinitions.Get(augmentableEffect.EffectType);
- bool canAugment = (effectDef != null && effectDef.CanBeAugmented);
- if (runecheck)
- {
- // Rune check if it is for the Rune UI, Augment if not
- canAugment = (effectDef != null && effectDef.CanBeRunified);
- }
+ // Rune check if it is for the Rune UI, Augment if not
+ bool canAugment = runecheck
+ ? CanRunifyEffect(magicItem, index)
+ : MagicItemEffectDefinitions.TryGet(augmentableEffect.EffectType, out MagicItemEffectDefinition effectDef) &&
+ effectDef.CanBeAugmented;
string text = AugmentHelper.GetAugmentSelectorText(magicItem, index, augmentableEffects, rarity);
string color = EpicLoot.GetRarityColor(rarity);
diff --git a/EpicLoot/src/GamePatches/Attack_Patch.cs b/EpicLoot/src/GamePatches/Attack_Patch.cs
index c47ef2b0f..214cfe72f 100644
--- a/EpicLoot/src/GamePatches/Attack_Patch.cs
+++ b/EpicLoot/src/GamePatches/Attack_Patch.cs
@@ -1,25 +1,34 @@
-using HarmonyLib;
+using HarmonyLib;
namespace EpicLoot
{
[HarmonyPatch]
public static class Attack_Patch
{
+ // The local player's melee attack in progress. DoMeleeAttack also runs for every creature this client
+ // owns, and a creature's swing lands on the local player synchronously -- so recording those made a
+ // Reflect (or anything else that reads this during the victim-side handling) resolve the creature's
+ // natural attack as "the local player's weapon".
public static Attack ActiveAttack = null;
[HarmonyPatch(typeof(Attack), nameof(Attack.DoMeleeAttack))]
[HarmonyPrefix]
[HarmonyPriority(Priority.Last)]
- public static void Attack_DoMeleeAttack_Prefix(Attack __instance)
+ public static void Attack_DoMeleeAttack_Prefix(Attack __instance, out Attack __state)
{
- ActiveAttack = __instance;
+ __state = ActiveAttack;
+ if (__instance.m_character != null && __instance.m_character == Player.m_localPlayer)
+ {
+ ActiveAttack = __instance;
+ }
}
+ // A finalizer, so an exception inside the attack cannot leave a stale attack behind.
[HarmonyPatch(typeof(Attack), nameof(Attack.DoMeleeAttack))]
- [HarmonyPostfix]
- public static void Attack_DoMeleeAttack_Postfix()
+ [HarmonyFinalizer]
+ public static void Attack_DoMeleeAttack_Finalizer(Attack __state)
{
- ActiveAttack = null;
+ ActiveAttack = __state;
}
}
}
diff --git a/EpicLoot/src/GamePatches/ItemDrop_Patch.cs b/EpicLoot/src/GamePatches/ItemDrop_Patch.cs
index 8f319d1ed..6c8ef38ac 100644
--- a/EpicLoot/src/GamePatches/ItemDrop_Patch.cs
+++ b/EpicLoot/src/GamePatches/ItemDrop_Patch.cs
@@ -59,6 +59,33 @@ public static void Postfix(ItemDrop __instance)
}
}
+ // ArmorStand cannot use LoadFromExternalZDO: it stores one item per slot, so it needs the indexed
+ // overload and calls the static LoadFromZDO directly, which means the patch above never fires for
+ // it. The breakage is the same one -- the stand re-instantiates the item from its prefab, so
+ // Awake/InitializeCustomData caches an empty MagicItemComponent, and only afterwards does
+ // LoadFromZDO swap in the saved m_customData. ItemInfo.Get returns that cached component ahead of
+ // re-reading m_customData, so the enchantment data sits on the item while its MagicItem stays null
+ // and the item reads as mundane -- until something rebuilds the ItemData (dropping it again clones
+ // it, which is why that "fixes" it).
+ // Only the indexed form needs this; index < 0 is ItemDrop.Load and LoadFromExternalZDO, both covered.
+ [HarmonyPatch(typeof(ItemDrop), nameof(ItemDrop.LoadFromZDO))]
+ public static class ItemDrop_LoadFromZDO_Patch
+ {
+ public static void Postfix(ItemDrop.ItemData itemData, int index)
+ {
+ if (index < 0 || itemData?.m_shared == null)
+ {
+ return;
+ }
+
+ itemData.Data().Get()?.Load();
+
+ // LoadFromZDO clears m_customData before repopulating it, so a shard taken off a stand can
+ // land here with its magic data gone. Its identity is in m_shared, so rebuild from that.
+ Shards.EnsureShardMetadata(itemData);
+ }
+ }
+
[HarmonyPatch(typeof(Inventory), nameof(Inventory.Load), typeof(ZPackage))]
public static class Inventory_Load_Patch
{
diff --git a/EpicLoot/src/GamePatches/TextsDialog_Patch.cs b/EpicLoot/src/GamePatches/TextsDialog_Patch.cs
index 257bd3cfe..6f43c94b7 100644
--- a/EpicLoot/src/GamePatches/TextsDialog_Patch.cs
+++ b/EpicLoot/src/GamePatches/TextsDialog_Patch.cs
@@ -29,41 +29,29 @@ private static void Postfix(TextsDialog __instance)
}
}
-[HarmonyPatch(typeof(TextsDialog), nameof(TextsDialog.OnSelectText))]
-internal static class TextsDialog_OnSelectText_Patch
+[HarmonyPatch(typeof(TextsDialog), nameof(TextsDialog.ShowText), typeof(TextsDialog.TextInfo))]
+internal static class TextsDialog_ShowText_Patch
{
- private static bool Prefix(TextsDialog __instance, TextsDialog.TextInfo text)
+ private static void Postfix(TextsDialog __instance, TextsDialog.TextInfo text)
{
if (!__instance.TryGetComponent(out MagicPages component))
{
- return true;
+ return;
}
component.Reset();
- if (text is not MagicTextInfo magicInfo)
- {
- return true;
- }
-
- component.OnSelectText(magicInfo);
-
- foreach (TextsDialog.TextInfo element in __instance.m_texts)
+ if (text is MagicTextInfo magicInfo)
{
- element.m_selected.SetActive(false);
+ component.OnSelectText(magicInfo);
}
-
- magicInfo.m_selected.SetActive(true);
-
- __instance.StartCoroutine(__instance.FocusOnCurrentLevel(__instance.m_leftScrollRect,
- __instance.m_listRoot, magicInfo.m_selected.transform as RectTransform));
- return false;
}
}
[HarmonyPatch(typeof(TextsDialog), nameof(TextsDialog.Setup))]
internal static class TextsDialog_Setup_Patch
{
- private static void Postfix(TextsDialog __instance) => __instance.GetComponent()?.Reset();
+ // Prefix, because Setup ends in ShowText(0) and a postfix would wipe the page it just built.
+ private static void Prefix(TextsDialog __instance) => __instance.GetComponent()?.Reset();
}
[HarmonyPatch(typeof(InventoryGui), nameof(InventoryGui.Hide))]
diff --git a/EpicLoot/src/GamePatches/TraderDiagnostics.cs b/EpicLoot/src/GamePatches/TraderDiagnostics.cs
index 052d0ddd6..4c2f239cd 100644
--- a/EpicLoot/src/GamePatches/TraderDiagnostics.cs
+++ b/EpicLoot/src/GamePatches/TraderDiagnostics.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using EpicLoot.Adventure;
@@ -61,6 +61,30 @@ private class PressRecord
/// Frame the store window was last actually opened, for the closed-too-soon check.
private static int _lastOpenedFrame = int.MinValue;
+ ///
+ /// The three inputs StoreGui.Update closes on, as they stood at the top of Update. Reading
+ /// them from the Hide prefix instead is quietly wrong: vanilla calls
+ /// ResetButtonStatus("JoyButtonB") on the line directly above its own Hide() call, so a
+ /// gamepad close always reported joyB=False and looked like it had been something else.
+ ///
+ private static int _closeInputFrame = int.MinValue;
+ private static bool _closeInputUse;
+ private static bool _closeInputEsc;
+ private static bool _closeInputJoyB;
+
+ ///
+ /// Called from the StoreGui.Update prefix in ,
+ /// which only gets this far while the window is open -- so this costs nothing on a frame
+ /// where there is no store to close.
+ ///
+ internal static void NoteUpdateCloseInputs(bool use, bool esc, bool joyB)
+ {
+ _closeInputFrame = Time.frameCount;
+ _closeInputUse = use;
+ _closeInputEsc = esc;
+ _closeInputJoyB = joyB;
+ }
+
internal static bool Recording => _press != null;
internal static void BeginPress(GameObject hovered, bool hold)
@@ -269,12 +293,26 @@ internal static void NoteHide(StoreGui storeGui)
? Vector3.Distance(storeGui.m_trader.transform.position, player.transform.position)
: -1f;
+ // Hide is also called from outside Update, where there is no snapshot to use. Say so
+ // rather than printing post-reset reads as if they were what Update acted on.
+ var fromUpdate = _closeInputFrame == Time.frameCount;
+ var use = fromUpdate ? _closeInputUse : ZInput.GetButtonDown("Use");
+ var esc = fromUpdate ? _closeInputEsc : ZInput.GetKeyDown(KeyCode.Escape);
+ var joyB = fromUpdate ? _closeInputJoyB : ZInput.GetButtonDown("JoyButtonB");
+
+ // Update's condition needs the viewer to exist *and* be hidden, so "is it null" on its
+ // own never said which of the two we were looking at.
+ var textViewer = TextViewer.instance == null ? "none"
+ : TextViewer.instance.IsVisible() ? "visible"
+ : "hidden";
+
EpicLoot.LogWarningForce(
$"[Trader] The store window closed {sinceOpen} frame(s) after it opened. " +
$"traderNull={storeGui.m_trader == null} distance={distance:F1}/{storeGui.m_hideDistance} " +
$"invGui={InventoryGui.IsVisible()} map={Minimap.IsOpen()} " +
- $"use={ZInput.GetButtonDown("Use")} esc={ZInput.GetKeyDown(KeyCode.Escape)} " +
- $"joyB={ZInput.GetButtonDown("JoyButtonB")} textViewer={(TextViewer.instance != null)}\n" +
+ $"use={use} esc={esc} joyB={joyB}" +
+ $"{(fromUpdate ? "" : " (read at Hide, not at the top of Update)")} " +
+ $"textViewer={textViewer}\n" +
new StackTrace(1, false));
}
diff --git a/EpicLoot/src/GamePatches/TraderStoreHardening.cs b/EpicLoot/src/GamePatches/TraderStoreHardening.cs
index 6e78a7d17..c0b9a5906 100644
--- a/EpicLoot/src/GamePatches/TraderStoreHardening.cs
+++ b/EpicLoot/src/GamePatches/TraderStoreHardening.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
+using UnityEngine;
namespace EpicLoot
{
@@ -132,4 +133,129 @@ public static void Prefix(StoreGui __instance, Trader __0)
TraderDiagnostics.NoteRepaired();
}
}
+
+ ///
+ /// Vanilla StoreGui.Update closes the window on `ZInput.GetButtonDown("Use")` -- the very press
+ /// that just opened it. Nothing consumes that press: GetButtonDown is a frame-latched flag that
+ /// any number of callers read, and Player.Interact leaves it set. So whether the store survives
+ /// its own opening frame comes down to which of Player.Update and StoreGui.Update Unity happens
+ /// to run first, and neither script declares an execution order:
+ ///
+ /// StoreGui.Update first -- the panel is still inactive, Update returns at the top. Fine, and
+ /// this is the order the game normally ends up in.
+ /// Player.Update first -- Show activates the panel, then Update sees it active with the press
+ /// still latched and hides it again in the same frame.
+ ///
+ /// The second order draws nothing at all (activated and deactivated between two renders) and
+ /// Hide() logs nothing, so it reaches us as "Use on the trader does nothing, and there is no
+ /// error in the log". Relogging rebuilds both objects and can put the order back; a zone reload
+ /// cannot, which is exactly the shape of the reports.
+ ///
+ /// Vanilla already knows about this hazard: InventoryGui guards the identical branch with
+ /// `(m_shownFrames > 1) & flag2`, refusing to close on the frame it opened. StoreGui has no
+ /// m_shownFrames at all. This gives it that guard the way InventoryGui itself does it, with
+ /// ZInput.ResetButtonStatus. Player.Interact has already consumed the press to open the store by
+ /// the time this runs, so nothing later in the frame is left owing it.
+ ///
+ /// On a client that wins the ordering race this is inert: Show has not run yet when the prefix
+ /// does, so the opening frame is never the current one.
+ ///
+ [HarmonyPatch(typeof(StoreGui))]
+ public static class StoreGui_KeepOpenOnItsOpeningFrame
+ {
+ /// Frame the window last went from closed to open. Never Time.frameCount otherwise.
+ private static int _openedFrame = int.MinValue;
+
+ /// Was the window already open when the Show now running started?
+ private static bool _wasOpenBeforeShow;
+
+ /// One line per session: the Update order does not change once it has been seen.
+ private static bool _reported;
+
+ ///
+ /// Update is private, which is the kind of member a game update quietly renames. Gating the
+ /// whole class on it keeps a miss from throwing out of CreateAndPatchAll, and leaves the Show
+ /// bookkeeping off too -- it is of no use on its own.
+ ///
+ public static bool Prepare()
+ {
+ return AccessTools.Method(typeof(StoreGui), nameof(StoreGui.Update)) != null;
+ }
+
+ [HarmonyPatch(nameof(StoreGui.Show))]
+ [HarmonyPrefix]
+ [HarmonyPriority(Priority.First)]
+ public static void Show_Prefix(StoreGui __instance)
+ {
+ _wasOpenBeforeShow = IsOpen(__instance);
+ }
+
+ ///
+ /// A finalizer rather than a postfix, for the same reason the adventure panel uses one:
+ /// FillList can throw after Show has already activated the window, and a half-filled window
+ /// still on screen is the one worth protecting -- letting the same press close it again is
+ /// how that throw went unreported in the first place.
+ ///
+ /// Only the closed-to-open transition counts. Show also runs its body for a press aimed at a
+ /// different trader while the window is up, and pressing Use again on the same trader is the
+ /// player asking to close it; recording either of those would swallow a close the player
+ /// meant.
+ ///
+ [HarmonyPatch(nameof(StoreGui.Show))]
+ [HarmonyFinalizer]
+ [HarmonyPriority(Priority.Last)]
+ public static void Show_Finalizer(StoreGui __instance)
+ {
+ if (!_wasOpenBeforeShow && IsOpen(__instance))
+ {
+ _openedFrame = Time.frameCount;
+ }
+ }
+
+ [HarmonyPatch(nameof(StoreGui.Update))]
+ [HarmonyPrefix]
+ [HarmonyPriority(Priority.First)]
+ public static void Update_Prefix(StoreGui __instance)
+ {
+ // Vanilla's own first act is to return here, and every Hide it can reach is below that
+ // line, so a closed store costs one bool per frame and nothing else.
+ if (!IsOpen(__instance))
+ {
+ return;
+ }
+
+ var use = ZInput.GetButtonDown("Use");
+
+ // Read here, before vanilla's ResetButtonStatus("JoyButtonB") on the line above its own
+ // Hide() call makes the gamepad close unreadable from the Hide prefix.
+ TraderDiagnostics.NoteUpdateCloseInputs(use, ZInput.GetKeyDown(KeyCode.Escape),
+ ZInput.GetButtonDown("JoyButtonB"));
+
+ if (!use || Time.frameCount != _openedFrame)
+ {
+ return;
+ }
+
+ ZInput.ResetButtonStatus("Use");
+
+ if (_reported)
+ {
+ return;
+ }
+
+ _reported = true;
+ EpicLoot.LogWarningForce(
+ "[Trader] This client runs Player.Update before StoreGui.Update, so the Use press that " +
+ "opens the store is still latched when StoreGui.Update reads it, and vanilla would have " +
+ "closed the window in the same frame it opened -- with nothing drawn and nothing logged. " +
+ "Swallowing that one press so the window stays open, which is the guard InventoryGui " +
+ "already has and StoreGui does not. If the trader looked like it was ignoring you, this " +
+ "was why.");
+ }
+
+ private static bool IsOpen(StoreGui storeGui)
+ {
+ return storeGui != null && storeGui.m_rootPanel != null && storeGui.m_rootPanel.activeSelf;
+ }
+ }
}
diff --git a/EpicLoot/src/GamePatches/ZNet_Patch.cs b/EpicLoot/src/GamePatches/ZNet_Patch.cs
index ed740d71d..7a862456a 100644
--- a/EpicLoot/src/GamePatches/ZNet_Patch.cs
+++ b/EpicLoot/src/GamePatches/ZNet_Patch.cs
@@ -12,6 +12,7 @@ public static void Postfix(ZNet __instance)
__instance.gameObject.AddComponent();
AdventureDataManager.Bounties.RegisterRPC(__instance.m_routedRpc);
+ AdventureSpawnSaveMarker.RegisterRPC(__instance.m_routedRpc);
}
}
diff --git a/EpicLoot/src/GatedItemType/GatedItemTypeHelper.cs b/EpicLoot/src/GatedItemType/GatedItemTypeHelper.cs
index abf7685e3..b5eb24c64 100644
--- a/EpicLoot/src/GatedItemType/GatedItemTypeHelper.cs
+++ b/EpicLoot/src/GatedItemType/GatedItemTypeHelper.cs
@@ -587,6 +587,35 @@ private static string GetBossKeyForMode(string bossKey, GatedItemTypeMode mode)
return bossKey;
}
+ ///
+ /// Whether the mode holds a whole biome tier back until its bosses are down. Only the two
+ /// BossKill modes do: Unlimited gates nothing, and the recipe/crafted modes gate item by item
+ /// (known recipe, crafted material) while treating every boss tier as reachable -- the same
+ /// split DetermineValidBosses makes.
+ ///
+ public static bool UsesBossBiomeCap(GatedItemTypeMode mode)
+ {
+ return mode != GatedItemTypeMode.Unlimited &&
+ mode != GatedItemTypeMode.PlayerMustKnowRecipe &&
+ mode != GatedItemTypeMode.PlayerMustHaveCraftedItem;
+ }
+
+ ///
+ /// True when a loot roll naming this exact item would hand that item out under the mode, rather
+ /// than replacing it with a gated substitute or dropping it (a denied prop). The same per-item
+ /// check GetGatedItemNameFromItemOrType makes; an item iteminfo.json does not list is ungated.
+ /// Rolls nothing, so previews can use it.
+ ///
+ public static bool IsItemAvailableUngated(string itemName, GatedItemTypeMode mode)
+ {
+ if (string.IsNullOrEmpty(itemName) || LootDenyList.IsDenied(itemName))
+ {
+ return false;
+ }
+
+ return mode == GatedItemTypeMode.Unlimited || !CheckIfItemNeedsGate(mode, itemName, out _);
+ }
+
///
/// Returns a valid biome defined in the BiomesInOrder list based off the GatedItemTypeMode.
///
@@ -599,7 +628,7 @@ public static Heightmap.Biome GetCurrentOrLowerBiomeByDefeatedBossSettings(Heigh
return biome;
}
- if (mode == GatedItemTypeMode.Unlimited || mode == GatedItemTypeMode.PlayerMustKnowRecipe)
+ if (!UsesBossBiomeCap(mode))
{
return biome;
}
diff --git a/EpicLoot/src/Loot/LootRoller.cs b/EpicLoot/src/Loot/LootRoller.cs
index 8c0f5bea0..66c1c5936 100644
--- a/EpicLoot/src/Loot/LootRoller.cs
+++ b/EpicLoot/src/Loot/LootRoller.cs
@@ -424,8 +424,11 @@ public static Dictionary GetLootTableChances(Vector3 location, Lis
looteqrare.Add(new LootDrop() { Item = ld.Item, Weight = ld.Weight, Rarity = [1] });
}
+ // Never ask a table for more than is still owed: several tables share one category,
+ // and a pass after a gating failure used to roll the full per-table count again,
+ // so an identify handed back more items than it was paid for.
_weightedLootTable.Setup(looteqrare.ToArray(), x => x.Weight);
- List selectedDrops = _weightedLootTable.Roll(lootPerCategory);
+ List selectedDrops = _weightedLootTable.Roll(Math.Min(lootPerCategory, numResults - results.Count));
EpicLoot.Log($"Available Loot ({lt.Loot.Length}) for table: {lt.Object}");
foreach (LootDrop lootDrop in lt.Loot)
@@ -438,6 +441,11 @@ public static Dictionary GetLootTableChances(Vector3 location, Lis
EpicLoot.Log($"Selected Drops from: {lt.Object} - {selectedDrops.Count}");
foreach (LootDrop lootDrop in selectedDrops)
{
+ if (results.Count >= numResults)
+ {
+ break;
+ }
+
string itemName = !string.IsNullOrEmpty(lootDrop?.Item) ? lootDrop.Item : "Invalid Item Name";
int rarityLength = lootDrop?.Rarity?.Length != null ? lootDrop.Rarity.Length : -1;
EpicLoot.Log($"Item: {itemName} - Rarity Count: {rarityLength} - Weight: {lootDrop.Weight}");
@@ -1122,7 +1130,14 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI
{
var cheatLegendary = !string.IsNullOrEmpty(CheatForceLegendary);
var cheatMythic = !string.IsNullOrEmpty(CheatForceMythic);
-
+
+ // Every rolled value is multiplied by this, so a mis-authored upgrade value (0 or negative
+ // in enchantingupgrades.json) would otherwise zero out the whole item.
+ if (float.IsNaN(powerlevelMod) || powerlevelMod <= 0f)
+ {
+ powerlevelMod = 1f;
+ }
+
if (cheatMythic)
{
rarity = ItemRarity.Mythic;
@@ -1189,8 +1204,7 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI
foreach (var guaranteedMagicEffect in itemInfo.GuaranteedMagicEffects)
{
- var effectDef = MagicItemEffectDefinitions.Get(guaranteedMagicEffect.Type);
- if (effectDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(guaranteedMagicEffect.Type, out var effectDef))
{
EpicLoot.LogError($"Could not find magic effect (Type={guaranteedMagicEffect.Type}) " +
$"while creating legendary/mythic item (ID={itemInfo.ID})");
@@ -1218,7 +1232,9 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI
_weightedEffectTable.Setup(availableEffects, x => x.SelectionWeight);
var effectDef = _weightedEffectTable.Roll();
- var effect = RollEffect(effectDef, magicItem.Rarity);
+ // Same power scaling as the guaranteed effects above. Only identification passes
+ // anything but 1 (the Sacrifice upgrade's "identified item power").
+ var effect = RollEffect(effectDef, magicItem.Rarity, null, powerlevelMod);
magicItem.Effects.Add(effect);
}
@@ -1783,7 +1799,7 @@ public static List RollAugmentEffects(ItemDrop.ItemData item, M
var currentEffect = magicItem.Effects[effectIndex];
- var valuelessEffect = MagicItemEffectDefinitions.IsValuelessEffect(currentEffect.EffectType, rarity);
+ var valuelessEffect = MagicItemEffectDefinitions.IsValuelessEffect(currentEffect.EffectType);
var availableEffects = MagicItemEffectDefinitions.GetAvailableEffects(item, magicItem, valuelessEffect ?
-1 : effectIndex);
@@ -1822,7 +1838,7 @@ public static List RollAugmentEffects(ItemDrop.ItemData item, M
results.Add(newEffect);
currentEffectTypes.Add(newEffect.EffectType);
- var newEffectIsValueless = MagicItemEffectDefinitions.IsValuelessEffect(newEffect.EffectType, rarity);
+ var newEffectIsValueless = MagicItemEffectDefinitions.IsValuelessEffect(newEffect.EffectType);
if (newEffectIsValueless)
{
availableEffects.RemoveAll(x => x.Type == newEffect.EffectType);
diff --git a/EpicLoot/src/Magic/ItemDataExtensions.cs b/EpicLoot/src/Magic/ItemDataExtensions.cs
index 1b0236d9b..be7006a27 100644
--- a/EpicLoot/src/Magic/ItemDataExtensions.cs
+++ b/EpicLoot/src/Magic/ItemDataExtensions.cs
@@ -207,8 +207,10 @@ public static bool CanBeAugmented(this ItemDrop.ItemData itemData)
return false;
}
- return itemData.GetMagicItem().Effects.Select(effect => MagicItemEffectDefinitions.Get(effect.EffectType))
- .Any(effectDef => effectDef.CanBeAugmented);
+ // An effect with no registered definition is never augmentable.
+ return itemData.GetMagicItem().Effects.Any(effect =>
+ MagicItemEffectDefinitions.TryGet(effect.EffectType, out MagicItemEffectDefinition effectDef) &&
+ effectDef.CanBeAugmented);
}
public static bool CanBeRunified(this ItemDrop.ItemData itemData)
@@ -218,8 +220,10 @@ public static bool CanBeRunified(this ItemDrop.ItemData itemData)
return false;
}
- return itemData.GetMagicItem().Effects.Select(effect => MagicItemEffectDefinitions.Get(effect.EffectType))
- .Any(effectDef => effectDef.CanBeRunified);
+ // An effect with no registered definition is never extracted or overwritten by a rune.
+ return itemData.GetMagicItem().Effects.Any(effect =>
+ MagicItemEffectDefinitions.TryGet(effect.EffectType, out MagicItemEffectDefinition effectDef) &&
+ effectDef.CanBeRunified);
}
// Shardstones and Brokkr's Gifts carry a cosmetic MagicItem -- a rarity and nothing else -- purely
@@ -316,6 +320,10 @@ public static List GetMundaneSetPieces(string setName)
{
// TODO: improve performace of this call
List results = new List();
+ // One entry per display token, not per prefab. Deep North registers SP_/FW_ NPC prop copies of
+ // set armour (SP_ArmorTrollLeatherChest, FW_CapeTrollHide, ...) that keep the real item's
+ // m_name and m_setName, so without this the troll set listed its tunic, pants and cape three times.
+ HashSet seen = new HashSet();
foreach (GameObject itemPrefab in ObjectDB.instance.m_items)
{
if (itemPrefab == null)
@@ -331,7 +339,7 @@ public static List GetMundaneSetPieces(string setName)
continue;
}
- if (itemDrop.m_itemData.m_shared.m_setName == setName)
+ if (itemDrop.m_itemData.m_shared.m_setName == setName && seen.Add(itemDrop.m_itemData.m_shared.m_name))
{
results.Add(itemDrop.m_itemData.m_shared.m_name);
}
@@ -561,9 +569,7 @@ private static string GetSetTooltip(ItemDrop.ItemData item, string setID, int se
foreach (SetBonusInfo setBonusInfo in setInfo.SetBonuses.OrderBy(x => x.Count))
{
bool hasEquipped = currentSetEquipped.Count >= setBonusInfo.Count;
- MagicItemEffectDefinition effectDef = MagicItemEffectDefinitions.Get(setBonusInfo.Effect.Type);
-
- if (effectDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(setBonusInfo.Effect.Type, out MagicItemEffectDefinition effectDef))
{
EpicLoot.LogError($"Set Tooltip: Could not find effect ({setBonusInfo.Effect.Type}) " +
$"for set ({setInfo.ID}) bonus ({setBonusInfo.Count})!");
diff --git a/EpicLoot/src/Magic/MagicItem.cs b/EpicLoot/src/Magic/MagicItem.cs
index 21fcee97d..77d6b9dbf 100755
--- a/EpicLoot/src/Magic/MagicItem.cs
+++ b/EpicLoot/src/Magic/MagicItem.cs
@@ -368,7 +368,8 @@ public bool HasAnyEffect(IEnumerable effectTypes)
public bool CanBeDisenchanted()
{
- return !Effects.Any(x => !MagicItemEffectDefinitions.Get(x.EffectType)?.CanBeDisenchanted ?? false);
+ // An effect with no registered definition does not block disenchanting.
+ return Effects.All(x => !MagicItemEffectDefinitions.TryGet(x.EffectType, out var def) || def.CanBeDisenchanted);
}
// Per-effect override that supplies the full ordered set of {0},{1},... format args for an
@@ -505,9 +506,7 @@ public static string GetEffectText(MagicItemEffect effect, ItemRarity rarity,
{
var effectDef = MagicItemEffectDefinitions.Get(effect.EffectType);
var result = GetEffectText(effectDef, effect.EffectValue);
- var values = valuesOverride ?? (string.IsNullOrEmpty(legendaryID) ?
- effectDef.GetValuesForRarity(rarity) :
- UniqueLegendaryHelper.GetLegendaryEffectValues(legendaryID, effect.EffectType));
+ var values = MagicItemEffectDefinitions.GetRollRange(effectDef, rarity, legendaryID, valuesOverride);
if (showRange && values != null)
{
if (!Mathf.Approximately(values.MinValue, values.MaxValue))
@@ -537,15 +536,12 @@ public static string GetEffectText(MagicItemEffect effect, MagicItemEffectDefini
public static string GetEffectDetailBlock(MagicItemEffect effect, ItemRarity rarity,
string legendaryID, MagicItemEffectDefinition.ValueDef valuesOverride, string indent)
{
- var effectDef = MagicItemEffectDefinitions.Get(effect.EffectType);
- if (effectDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(effect.EffectType, out var effectDef))
{
return string.Empty;
}
- var values = valuesOverride ?? (string.IsNullOrEmpty(legendaryID) ?
- effectDef.GetValuesForRarity(rarity) :
- UniqueLegendaryHelper.GetLegendaryEffectValues(legendaryID, effect.EffectType));
+ var values = MagicItemEffectDefinitions.GetRollRange(effectDef, rarity, legendaryID, valuesOverride);
var block = new StringBuilder();
@@ -607,9 +603,64 @@ public void ReplaceEffect(int index, MagicItemEffect newEffect)
SetEffectAsAugmented(index);
+ // The augment dialog's "keep" choice hands back the very same effect object; anything else
+ // is a new roll, and the tempered marker belonged to the value it replaces. The value
+ // comparison only covers a keep whose effect was re-read from the item in between.
+ var oldEffect = Effects[index];
+ var unchanged = ReferenceEquals(oldEffect, newEffect) ||
+ (oldEffect != null && newEffect != null && oldEffect.EffectType == newEffect.EffectType &&
+ Mathf.Approximately(oldEffect.EffectValue, newEffect.EffectValue));
+ if (!unchanged)
+ {
+ TemperedEffectIndices.Remove(index);
+ }
+
Effects[index] = newEffect;
}
+ // Removes the effect at index and keeps the per-index history (augmented and tempered markers)
+ // pointing at the same effects: the removed index's markers go with it and every later index
+ // shifts down one.
+ public void RemoveEffectAt(int index)
+ {
+ if (index < 0 || index >= Effects.Count)
+ {
+ EpicLoot.LogError("Tried to remove effect on magic item outside of the range of the effects list!");
+ return;
+ }
+
+ Effects.RemoveAt(index);
+
+ if (AugmentedEffectIndex == index)
+ {
+ AugmentedEffectIndex = -1;
+ }
+ else if (AugmentedEffectIndex > index)
+ {
+ AugmentedEffectIndex--;
+ }
+
+ RemoveHistoryIndex(AugmentedEffectIndices, index);
+ RemoveHistoryIndex(TemperedEffectIndices, index);
+ }
+
+ private static void RemoveHistoryIndex(List indices, int removedIndex)
+ {
+ if (indices == null)
+ {
+ return;
+ }
+
+ indices.RemoveAll(x => x == removedIndex);
+ for (var i = 0; i < indices.Count; i++)
+ {
+ if (indices[i] > removedIndex)
+ {
+ indices[i]--;
+ }
+ }
+ }
+
public bool HasBeenAugmented()
{
return AugmentedEffectIndex >= 0 && AugmentedEffectIndex < Effects.Count || AugmentedEffectIndices.Count > 0;
@@ -659,8 +710,8 @@ public string GetFirstEquipEffect(out FxAttachMode mode)
{
foreach (var effect in Effects)
{
- var effectDef = MagicItemEffectDefinitions.Get(effect.EffectType);
- if (effectDef != null && !string.IsNullOrEmpty(effectDef.EquipFx))
+ if (MagicItemEffectDefinitions.TryGet(effect.EffectType, out var effectDef) &&
+ !string.IsNullOrEmpty(effectDef.EquipFx))
{
mode = effectDef.EquipFxMode;
return effectDef.EquipFx;
diff --git a/EpicLoot/src/Magic/MagicItemComponent.cs b/EpicLoot/src/Magic/MagicItemComponent.cs
index f6103bebd..ffb210752 100755
--- a/EpicLoot/src/Magic/MagicItemComponent.cs
+++ b/EpicLoot/src/Magic/MagicItemComponent.cs
@@ -2,6 +2,7 @@
using EpicLoot.MagicItemEffects;
using Newtonsoft.Json;
using System;
+using System.Collections.Generic;
using UnityEngine;
namespace EpicLoot;
@@ -157,6 +158,15 @@ public override void Unload()
Indestructible.Revert(Item);
}
+ // A binary effect (Indestructible, Weightless, Throwable...) must sit at the default value of 1:
+ // older saves and hand-edited data can carry a stale number that would otherwise show up in the
+ // tooltip. "Binary" is deliberately judged across every rarity rather than at this item's own
+ // rarity -- Load() writes the result straight back to m_customData, so a per-rarity test would
+ // permanently flatten a legitimately rolled value whenever the loaded definitions simply lack a
+ // block for that rarity. That happens for real: a dedicated server replaces the client's
+ // definitions wholesale on connect (ELConfig.ApplyClientConfig), so a server config that predates
+ // a newly added rarity would otherwise reset every effect on every item of that rarity to 1 --
+ // once, silently, and irreversibly. Report the gap instead, and leave the value alone.
private void FixupValuelessEffects()
{
if (MagicItem == null)
@@ -166,13 +176,45 @@ private void FixupValuelessEffects()
foreach (MagicItemEffect effect in MagicItem.Effects)
{
- if (MagicItemEffectDefinitions.IsValuelessEffect(effect.EffectType, MagicItem.Rarity) &&
- !Mathf.Approximately(effect.EffectValue, 1))
+ if (MagicItemEffectDefinitions.IsValuelessEffect(effect.EffectType))
{
- EpicLoot.Log($"Fixing up effect on {MagicItem.DisplayName}: effect={effect.EffectType}");
- effect.EffectValue = 1;
+ if (!Mathf.Approximately(effect.EffectValue, 1))
+ {
+ EpicLoot.Log($"Fixing up effect on {MagicItem.DisplayName}: effect={effect.EffectType}");
+ effect.EffectValue = 1;
+ }
+
+ continue;
}
+
+ WarnOnceAboutMissingRarityValues(effect.EffectType, MagicItem.Rarity);
+ }
+ }
+
+ // Types already reported by WarnOnceAboutMissingRarityValues. Every item entering the world passes
+ // through Load(), so an unguarded warning would repeat per item per load; the interesting fact is
+ // which effect/rarity pairs have no values, and that is a property of the config, not of the item.
+ private static readonly HashSet ReportedRarityGaps = new HashSet();
+
+ private static void WarnOnceAboutMissingRarityValues(string effectType, ItemRarity rarity)
+ {
+ // An unknown effect is not a rarity gap; Get() reports the missing definition itself.
+ if (!MagicItemEffectDefinitions.TryGet(effectType, out var effectDef) ||
+ effectDef.ValuesPerRarity?.GetValueDefForRarity(rarity) != null)
+ {
+ return;
}
+
+ if (!ReportedRarityGaps.Add($"{effectType}/{rarity}"))
+ {
+ return;
+ }
+
+ // Forced: this is a silent data problem otherwise -- the effect renders with a blank value
+ // everywhere (tooltip, enchant preview) and cannot be rolled at this rarity.
+ EpicLoot.LogWarningForce($"Enchantment '{effectType}' has no values for rarity {rarity} in the " +
+ "loaded magiceffects.json. Items carrying it at that rarity will show a blank value. If you " +
+ "are on a dedicated server, the server's copy of magiceffects.json is the one in use.");
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffectDefinition.cs b/EpicLoot/src/Magic/MagicItemEffectDefinition.cs
index c510af173..69c588feb 100755
--- a/EpicLoot/src/Magic/MagicItemEffectDefinition.cs
+++ b/EpicLoot/src/Magic/MagicItemEffectDefinition.cs
@@ -1,6 +1,7 @@
using EpicLoot.Crafting;
using EpicLoot.GatedItemType;
using EpicLoot.General;
+using EpicLoot.LegendarySystem;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
@@ -584,6 +585,8 @@ public static class MagicItemEffectDefinitions
public static void Initialize(MagicItemEffectsList config)
{
AllDefinitions.Clear();
+ // A reload may drop definitions that were present before; let each one warn again.
+ FallbackDefinitions.Clear();
foreach (var magicItemEffectDefinition in config.MagicItemEffects)
{
Add(magicItemEffectDefinition);
@@ -606,10 +609,39 @@ public static void Add(MagicItemEffectDefinition effectDef)
AllDefinitions.Add(effectDef.Type, effectDef);
}
+ // Stand-ins handed out by Get() for types with no registered definition, one per type so the
+ // "definition missing" warning is logged once rather than on every tooltip redraw.
+ private static readonly Dictionary FallbackDefinitions =
+ new Dictionary();
+
+ ///
+ /// The registered definition for , if there is one. Use this wherever an
+ /// unknown effect must be left out (rolling, tempering, rune extraction, augmenting): unlike
+ /// it never fabricates a stand-in.
+ ///
+ public static bool TryGet(string type, out MagicItemEffectDefinition effectDef)
+ {
+ effectDef = null;
+ return type != null && AllDefinitions.TryGetValue(type, out effectDef) && effectDef != null;
+ }
+
+ ///
+ /// Never null: an unknown type gets a synthesized stand-in (NoRoll, blank display text, generic
+ /// value ranges) so display code cannot NRE on an effect whose definition was removed. Anything
+ /// that acts on the definition rather than showing it should use .
+ ///
public static MagicItemEffectDefinition Get(string type)
{
- AllDefinitions.TryGetValue(type, out MagicItemEffectDefinition effectDef);
- if (effectDef == null) {
+ // Registered definitions are checked first on every call, so one that arrives after a
+ // fallback was handed out (a late API registration, a config reload) takes over at once.
+ if (TryGet(type, out MagicItemEffectDefinition effectDef))
+ {
+ return effectDef;
+ }
+
+ string key = type ?? string.Empty;
+ if (!FallbackDefinitions.TryGetValue(key, out effectDef))
+ {
EpicLoot.LogWarning($"Enchantment definition missing for: {type}");
effectDef = new MagicItemEffectDefinition() {
ValuesPerRarity = new MagicItemEffectDefinition.ValuesPerRarityDef() {
@@ -623,10 +655,61 @@ public static MagicItemEffectDefinition Get(string type)
Requirements = new MagicItemEffectRequirements() { NoRoll = true },
Type = type,
};
+ FallbackDefinitions[key] = effectDef;
}
+
return effectDef;
}
+ ///
+ /// The value range an effect on an item rolls and tempers against. In order: an explicit
+ /// override (the shard preview passes a zero-width one on purpose), then the guaranteed-effect
+ /// Values block of the item's legendary/mythic when it has one, then the rarity table. Stock
+ /// legendaries.json gives no guaranteed effect a Values block, so a unique's effects normally
+ /// land on the rarity table too. Null means the effect carries no value at that rarity.
+ ///
+ public static MagicItemEffectDefinition.ValueDef GetRollRange(MagicItemEffectDefinition def,
+ ItemRarity rarity, string legendaryID = null, MagicItemEffectDefinition.ValueDef valuesOverride = null)
+ {
+ if (valuesOverride != null)
+ {
+ return valuesOverride;
+ }
+
+ if (def == null)
+ {
+ return null;
+ }
+
+ if (!string.IsNullOrEmpty(legendaryID))
+ {
+ MagicItemEffectDefinition.ValueDef legendaryValues =
+ UniqueLegendaryHelper.GetLegendaryEffectValues(legendaryID, def.Type);
+ if (legendaryValues != null)
+ {
+ return legendaryValues;
+ }
+ }
+
+ return def.GetValuesForRarity(rarity);
+ }
+
+ ///
+ /// by type. An effect with no registered definition has no range (unless overridden).
+ public static MagicItemEffectDefinition.ValueDef GetRollRange(string effectType, ItemRarity rarity,
+ string legendaryID = null, MagicItemEffectDefinition.ValueDef valuesOverride = null)
+ {
+ return TryGet(effectType, out MagicItemEffectDefinition def)
+ ? GetRollRange(def, rarity, legendaryID, valuesOverride)
+ : valuesOverride;
+ }
+
+ /// The roll range of on this particular item.
+ public static MagicItemEffectDefinition.ValueDef GetRollRange(MagicItem magicItem, string effectType)
+ {
+ return magicItem == null ? null : GetRollRange(effectType, magicItem.Rarity, magicItem.LegendaryID);
+ }
+
public static Dictionary GetEffectConfig(string type)
{
AllDefinitions.TryGetValue(type, out var effectDef);
@@ -662,16 +745,41 @@ public static List GetAvailableEffects(
return results;
}
- public static bool IsValuelessEffect(string effectType, ItemRarity rarity)
+ ///
+ /// True only for an effect that carries no value at any rarity -- the binary grants
+ /// (Indestructible, Weightless, Warmth, Throwable...) whose magiceffects.json entry has no
+ /// ValuesPerRarity block at all.
+ ///
+ /// Deliberately not "has no value at rarity X": an effect with values at some rarities but not
+ /// that one is a config gap, not a binary effect, and treating the two the same is how a
+ /// legitimately rolled value gets destroyed (see MagicItemComponent.FixupValuelessEffects).
+ /// Callers that really do want the per-rarity question ask GetValuesForRarity directly.
+ ///
+ public static bool IsValuelessEffect(string effectType)
{
- var effectDef = Get(effectType);
- if (effectDef == null)
+ // An unknown effect is never treated as binary, so its stored value is left alone. Not warned
+ // here: every item load passes through this, and Get() already reports the missing
+ // definition once per type wherever the effect is shown.
+ if (!TryGet(effectType, out var effectDef))
+ {
+ return false;
+ }
+
+ if (effectDef.ValuesPerRarity == null)
{
- EpicLoot.LogWarning($"Checking if unknown effect is valuless ({effectType}/{rarity})");
+ EpicLoot.LogWarning($"Checking if effect without a ValuesPerRarity block is valueless ({effectType})");
return false;
}
- return effectDef.GetValuesForRarity(rarity) == null;
+ foreach (var rarity in Rarities.All)
+ {
+ if (effectDef.ValuesPerRarity.GetValueDefForRarity(rarity) != null)
+ {
+ return false;
+ }
+ }
+
+ return true;
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/AddLifeSteal.cs b/EpicLoot/src/Magic/MagicItemEffects/AddLifeSteal.cs
index af905968e..2852ed575 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/AddLifeSteal.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/AddLifeSteal.cs
@@ -13,16 +13,7 @@ public static void CheckAndDoLifeSteal(HitData hit, Character attacker)
return;
}
- ItemDrop.ItemData weapon;
- if (Attack_Patch.ActiveAttack != null && Attack_Patch.ActiveAttack.m_weapon != null)
- {
- weapon = Attack_Patch.ActiveAttack.m_weapon;
- }
- else
- {
- weapon = player.GetCurrentWeapon();
- }
-
+ ItemDrop.ItemData weapon = MagicEffectsHelper.GetActiveWeapon(player);
if (weapon == null || !weapon.IsMagic())
{
return;
diff --git a/EpicLoot/src/Magic/MagicItemEffects/AddSkillLevel.cs b/EpicLoot/src/Magic/MagicItemEffects/AddSkillLevel.cs
index 1b8d27342..c172294d8 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/AddSkillLevel.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/AddSkillLevel.cs
@@ -2,6 +2,7 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -84,13 +85,73 @@ void SkillsAsSkills(string effect, SkillType[] type, SkillType[] asType)
}
}
+ // The skill *level* vanilla reads for gameplay -- a hit's m_skillLevel, which sets the Staff of Protection
+ // bubble's absorb; a summon's damage factor and how many may be out at once (SpawnAbility) -- as opposed to
+ // the skill factor, which the patch above already raises. Adding the bonus to Skills.GetSkillLevel outright
+ // would count it twice in GetSkillFactor (which reads GetSkillLevel, then gets the bonus above) and in the
+ // skills panel, so it is only added for the duration of a gameplay read: Character.GetSkillLevel, which
+ // every attack, projectile, area effect and item tooltip goes through, and SpawnAbility's spawn coroutine,
+ // which reads Skills.GetSkillLevel directly. A GetSkillFactor inside such a read (SkillIncrease's
+ // skill-as-skill effects call it) suspends the read, for the same double-count reason.
+ [HarmonyPatch]
+ public static class AddSkillLevel_GameplaySkillLevel_Patch
+ {
+ private static int _gameplayReads;
+
+ [HarmonyPatch(typeof(Skills), nameof(Skills.GetSkillLevel))]
+ [HarmonyPostfix]
+ private static void Skills_GetSkillLevel_Postfix(Skills __instance, SkillType skillType, ref float __result)
+ {
+ if (_gameplayReads > 0 && __instance.m_player != null && skillType != SkillType.None)
+ {
+ __result += AddSkillLevel_Skills_GetSkillFactor_Patch.SkillIncrease(__instance.m_player, skillType);
+ }
+ }
+
+ [HarmonyPatch(typeof(Character), nameof(Character.GetSkillLevel))]
+ [HarmonyPrefix]
+ private static void Character_GetSkillLevel_Prefix() => _gameplayReads++;
+
+ [HarmonyPatch(typeof(Character), nameof(Character.GetSkillLevel))]
+ [HarmonyFinalizer]
+ private static void Character_GetSkillLevel_Finalizer() => _gameplayReads--;
+
+ [HarmonyPatch(typeof(Skills), nameof(Skills.GetSkillFactor))]
+ [HarmonyPrefix]
+ private static void Skills_GetSkillFactor_Prefix(out int __state)
+ {
+ __state = _gameplayReads;
+ _gameplayReads = 0;
+ }
+
+ [HarmonyPatch(typeof(Skills), nameof(Skills.GetSkillFactor))]
+ [HarmonyFinalizer]
+ private static void Skills_GetSkillFactor_Finalizer(int __state) => _gameplayReads = __state;
+
+ [HarmonyPatch]
+ private static class SpawnAbility_Spawn_Patch
+ {
+ [UsedImplicitly]
+ private static MethodBase TargetMethod() =>
+ AccessTools.EnumeratorMoveNext(AccessTools.Method(typeof(SpawnAbility), nameof(SpawnAbility.Spawn)));
+
+ [UsedImplicitly]
+ private static void Prefix() => _gameplayReads++;
+
+ [UsedImplicitly]
+ private static void Finalizer() => _gameplayReads--;
+ }
+ }
+
// These fix a bug in vanilla where skill factor cannot go over 100
[HarmonyPatch(typeof(Skills), nameof(Skills.GetRandomSkillRange))]
public static class Skills_GetRandomSkillRange_Patch
{
public static bool Prefix(Skills __instance, out float min, out float max, SkillType skillType)
{
- var skillValue = Mathf.Lerp(0.4f, 1.0f, __instance.GetSkillFactor(skillType));
+ // Unclamped: the factor is above 1 when an EpicLoot skill bonus takes a skill past 100, and a
+ // clamped Lerp threw that part away, so +skill added no damage at trained 100.
+ var skillValue = Mathf.LerpUnclamped(0.4f, 1.0f, __instance.GetSkillFactor(skillType));
min = Mathf.Max(0, skillValue - 0.15f);
max = skillValue + 0.15f;
return false;
diff --git a/EpicLoot/src/Magic/MagicItemEffects/AutoMeads.cs b/EpicLoot/src/Magic/MagicItemEffects/AutoMeads.cs
index 1ba930236..ec5c71584 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/AutoMeads.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/AutoMeads.cs
@@ -5,8 +5,14 @@ namespace EpicLoot.Magic.MagicItemEffects;
public class AutoMeads
{
- // Prefix handler invoked by CharacterDamageDispatch (victim-side): auto-drink a healing mead when the
- // incoming hit would drop the local player to critical health.
+ // True while AutoMeads is drinking for a hit that is about to drop the player to critical health. The
+ // player's health is still above the threshold at that moment, so Instant Mead (ModifyMeads) reads this
+ // as well as the current health -- otherwise the pre-emptive drink always came out as a slow mead.
+ public static bool DrinkingForCriticalHit { get; private set; }
+
+ // Invoked by SharedPlayerPostArmorDamagePatch (victim-side), once the hit has been through the block,
+ // resistances and armor: auto-drink one healing mead when the hit will drop the local player to critical
+ // health, or when they are already there.
public static void OnIncomingHit(Character __instance, HitData hit)
{
if (__instance is not Player player ||
@@ -28,7 +34,23 @@ public static void OnIncomingHit(Character __instance, HitData hit)
!player.m_seman.HaveStatusEffect(item.m_shared.m_consumeStatusEffect.NameHash()) &&
!player.m_seman.HaveStatusEffectCategory(item.m_shared.m_consumeStatusEffect.m_category))
{
- player.ConsumeItem(inventory, item);
+ bool consumed;
+ DrinkingForCriticalHit = true;
+ try
+ {
+ consumed = player.ConsumeItem(inventory, item);
+ }
+ finally
+ {
+ DrinkingForCriticalHit = false;
+ }
+
+ // One mead per hit: meads from other mods that don't share vanilla's category would
+ // otherwise all be drunk at once.
+ if (consumed)
+ {
+ return;
+ }
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/ChainLightning.cs b/EpicLoot/src/Magic/MagicItemEffects/ChainLightning.cs
index 21738e9f7..6174254cb 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/ChainLightning.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/ChainLightning.cs
@@ -1,4 +1,5 @@
-using UnityEngine;
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
+using UnityEngine;
namespace EpicLoot.MagicItemEffects {
@@ -10,7 +11,9 @@ public static void OnDamageDealt(Character __instance, HitData hit, Character at
}
var player = attacker as Player;
- var weapon = player?.GetCurrentWeapon();
+ // The weapon that dealt this hit -- for an arrow or a thrown weapon, the one that fired it -- not
+ // whatever is in the right hand now.
+ var weapon = MagicEffectsHelper.GetActiveWeapon(player);
if (weapon == null || weapon.GetMagicItem()?.HasEffect(nameof(MagicEffectType.ChainLightning), includeSocketed: true) != true) {
return;
}
@@ -47,6 +50,8 @@ private static void TriggerChainLightningEffect(Character target, Player player)
}
var instance = Object.Instantiate(prefab, target.transform.position, Quaternion.identity);
+ // Its hits (and every jump's, which clones this live object) are bonus hits, not weapon strikes.
+ HitSource.MarkBonusSource(instance);
var aoe = instance.GetComponent();
if (aoe != null) {
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Executioner.cs b/EpicLoot/src/Magic/MagicItemEffects/Executioner.cs
index 96139db16..efdacadc1 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/Executioner.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Executioner.cs
@@ -58,6 +58,13 @@ public static void ModifyOutgoingHit(Character __instance, HitData hit)
// equipped weapon. Melee hits derive from the live weapon per hit.
float multiplier = ExecutionerMultiplier ?? ReadExecutionerValue(player);
+ // No Executioner on the attack: nothing to spend, so the target stays executable by a later hit
+ // that does carry it.
+ if (multiplier <= 1f)
+ {
+ return;
+ }
+
if (__instance.GetHealth() / __instance.GetMaxHealth() < 0.2f)
{
hit.m_damage.Modify(multiplier);
diff --git a/EpicLoot/src/Magic/MagicItemEffects/ExplosiveArrows.cs b/EpicLoot/src/Magic/MagicItemEffects/ExplosiveArrows.cs
index 4a9e2f812..9e3d848d7 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/ExplosiveArrows.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/ExplosiveArrows.cs
@@ -1,4 +1,5 @@
using EpicLoot.General;
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
using HarmonyLib;
using Jotunn.Managers;
using System.Collections.Generic;
@@ -72,6 +73,16 @@ private static void SpawnExplosiveArrowOnHit(Vector3 hitPoint, Projectile instan
return;
}
+ // The explosion needs the shooter as its owner: every one of Aoe.ShouldHit's filters (the
+ // prefab's m_hitOwner / m_hitFriendly off) only applies when there is an owner, and the hits
+ // carry it as their attacker, which is what vanilla's PvP check in RPC_Damage keys on. An
+ // ownerless explosion hit every character in range -- players without PvP, tames and the
+ // shooter included. With no live shooter to own it, there is no explosion.
+ if (instance.m_owner == null)
+ {
+ return;
+ }
+
GameObject prefab = PrefabManager.Instance.GetPrefab(EpicAssets.ExplosiveArrow);
if (prefab == null)
@@ -90,6 +101,20 @@ private static void SpawnExplosiveArrowOnHit(Vector3 hitPoint, Projectile instan
return;
}
+ // Set directly rather than through Aoe.Setup, which would also apply the weapon's upgrade and
+ // world-level bonuses on top of the damage worked out below.
+ aoe.m_owner = instance.m_owner;
+
+ // PvP: the explosion hits what the arrow itself could (vanilla Projectile.IsValidTarget). With
+ // the archer's PvP off that is enemies only; with it on, friendlies as well, and vanilla's
+ // RPC_Damage then spares any player whose own PvP is off. Every player shares one m_name
+ // ("Human"), so m_hitSame has to follow too, or the same-kind filter drops them all.
+ bool pvp = instance.m_owner.IsPVPEnabled();
+ aoe.m_hitFriendly = pvp;
+ aoe.m_hitSame = pvp;
+ // Damage the effect deals on its own, not a weapon strike (see HitSource).
+ HitSource.MarkBonusSource(spawnedObject);
+
float explodingArrowStrength = explodingArrowValue * instance.m_damage.EpicLootGetTotalDamage();
aoe.m_damage.m_fire = explodingArrowStrength;
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/FrostAOE.cs b/EpicLoot/src/Magic/MagicItemEffects/FrostAOE.cs
index 064408147..a99270aa4 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/FrostAOE.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/FrostAOE.cs
@@ -1,4 +1,5 @@
using EpicLoot.General;
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
using HarmonyLib;
using JetBrains.Annotations;
using System.Collections;
@@ -146,7 +147,7 @@ private IEnumerator DamageCoroutine()
hitData.m_damage.m_frost = _frostDamage;
hitData.SetAttacker(_player);
- characterCollider.Damage(hitData);
+ HitSource.DealBonusDamage(characterCollider, hitData);
}
ZNetScene.instance.Destroy(gameObject);
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Helpers/DamageInRadius.cs b/EpicLoot/src/Magic/MagicItemEffects/Helpers/DamageInRadius.cs
index 1be641c7d..1b37cdd30 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Helpers/DamageInRadius.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Helpers/DamageInRadius.cs
@@ -27,7 +27,7 @@ public static void DamageEnemiesInRadius(Player attacker, Vector3 center, float
m_ranged = true,
};
hit.SetAttacker(attacker);
- character.Damage(hit);
+ HitSource.DealBonusDamage(character, hit);
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Helpers/ElementalHitVariant.cs b/EpicLoot/src/Magic/MagicItemEffects/Helpers/ElementalHitVariant.cs
new file mode 100644
index 000000000..3b64ab12a
--- /dev/null
+++ b/EpicLoot/src/Magic/MagicItemEffects/Helpers/ElementalHitVariant.cs
@@ -0,0 +1,21 @@
+namespace EpicLoot.src.Magic.MagicItemEffects.Helpers {
+ // HitData.m_variant picks which colour of an elemental status effect the target shows: Burning's start
+ // effects are vfx_Burning (0), vfx_Burning_blue (1) and vfx_Burning_green (2); Lightning's are fx_Lightning
+ // (0) and fx_Lightning_red (1). EffectList.Create spawns every entry when the variant is -1, and -1 is what
+ // an ordinary weapon carries (SharedData.m_hitVariant defaults to it). So fire or lightning added to such a
+ // weapon -- AddFireDamage, NecroticFire, the lightning conversions -- lit the target in every colour at once.
+ //
+ // An unset variant on a player's elemental hit is defaulted to 0, the ordinary colour. A weapon that sets
+ // its own variant (the FrostFire / BloodLightning golds, Fader's green fire) keeps it.
+ public static class ElementalHitVariant {
+ public static void ModifyOutgoingHit(HitData hit, Character attacker) {
+ if (hit.m_variant >= 0 || attacker is not Player) {
+ return;
+ }
+
+ if (hit.m_damage.m_fire > 0f || hit.m_damage.m_lightning > 0f) {
+ hit.m_variant = 0;
+ }
+ }
+ }
+}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Helpers/HitSource.cs b/EpicLoot/src/Magic/MagicItemEffects/Helpers/HitSource.cs
new file mode 100644
index 000000000..5878e1435
--- /dev/null
+++ b/EpicLoot/src/Magic/MagicItemEffects/Helpers/HitSource.cs
@@ -0,0 +1,142 @@
+using HarmonyLib;
+using UnityEngine;
+
+namespace EpicLoot.src.Magic.MagicItemEffects.Helpers {
+ // Where the hit Character.Damage is processing came from, for the attacker-side dispatcher
+ // (SharedCharacterDamagePatch) to tell weapon strikes apart from everything else.
+ //
+ // Bonus hits: damage EpicLoot deals on its own -- Reflect, the Eikthyr charge, FrostAOE, Trailblazer's fire,
+ // the Moder and Bonemass novas, the meteor and chain lightning -- carries the local player as attacker, so
+ // without a tag every weapon-strike effect (crits, double damage, Wager and Mercenary stakes, lifesteal,
+ // Executioner, Slow, Paralyze, Eikthyr charges) rolled on it as well, once per target it touched.
+ // * Direct damage goes through DealBonusDamage, which holds the tag for the call: Character.Damage runs the
+ // whole attacker-side dispatch synchronously.
+ // * Damage that lands later, from a spawned vanilla Aoe or Projectile, is tagged with MarkBonusSource. The
+ // patches below hold the tag while a marked object deals damage, and mark whatever such an object sets up
+ // while tagged (a projectile's spawn-on-hit). Chain lightning's jumps are Instantiate clones of the live
+ // strike, so they carry the marker from the start.
+ //
+ // Firing weapon: a projectile lands long after its attack finished, when the hands may hold something else --
+ // or nothing, for a thrown weapon, which vanilla unequips as it leaves the hand. While a local player's
+ // Projectile or Aoe deals damage, FiringWeapon is the weapon that fired it (Projectile.m_weapon /
+ // Aoe.m_itemData), which MagicEffectsHelper.GetActiveWeapon prefers over the hands.
+ public static class HitSource {
+ private static int _bonusDepth;
+
+ public static bool IsBonusHit => _bonusDepth > 0;
+
+ public static ItemDrop.ItemData FiringWeapon { get; private set; }
+
+ public static void DealBonusDamage(IDestructible target, HitData hit) {
+ _bonusDepth++;
+ try {
+ target.Damage(hit);
+ } finally {
+ _bonusDepth--;
+ }
+ }
+
+ public static void MarkBonusSource(GameObject source) {
+ if (source != null && !source.TryGetComponent(out BonusSource _)) {
+ source.AddComponent();
+ }
+ }
+
+ // Saved on entry to a hook and handed back on exit, so nested hooks (Projectile.OnHit calling DoAOE)
+ // restore what they found.
+ internal struct Scope {
+ public bool Bonus;
+ public ItemDrop.ItemData PreviousWeapon;
+ }
+
+ internal static Scope Enter(Component source, Character owner, ItemDrop.ItemData weapon) {
+ var scope = new Scope { PreviousWeapon = FiringWeapon };
+ if (source != null && source.TryGetComponent(out BonusSource _)) {
+ scope.Bonus = true;
+ _bonusDepth++;
+ }
+ if (weapon != null && owner != null && owner == Player.m_localPlayer) {
+ FiringWeapon = weapon;
+ }
+ return scope;
+ }
+
+ internal static void Exit(Scope scope) {
+ if (scope.Bonus) {
+ _bonusDepth--;
+ }
+ FiringWeapon = scope.PreviousWeapon;
+ }
+
+ internal static void MarkIfBonus(Component instance) {
+ if (IsBonusHit && instance != null) {
+ MarkBonusSource(instance.gameObject);
+ }
+ }
+
+ public sealed class BonusSource : MonoBehaviour {
+ }
+ }
+
+ [HarmonyPatch(typeof(Projectile), nameof(Projectile.OnHit))]
+ internal static class HitSource_Projectile_OnHit_Patch {
+ [HarmonyPrefix]
+ private static void Prefix(Projectile __instance, out HitSource.Scope __state) {
+ __state = HitSource.Enter(__instance, __instance.m_owner, __instance.m_weapon);
+ }
+
+ [HarmonyFinalizer]
+ private static void Finalizer(HitSource.Scope __state) => HitSource.Exit(__state);
+ }
+
+ // The in-flight damage path (m_hitMidFlight) reaches DoAOE from FixedUpdate rather than OnHit.
+ [HarmonyPatch(typeof(Projectile), nameof(Projectile.DoAOE))]
+ internal static class HitSource_Projectile_DoAOE_Patch {
+ [HarmonyPrefix]
+ private static void Prefix(Projectile __instance, out HitSource.Scope __state) {
+ __state = HitSource.Enter(__instance, __instance.m_owner, __instance.m_weapon);
+ }
+
+ [HarmonyFinalizer]
+ private static void Finalizer(HitSource.Scope __state) => HitSource.Exit(__state);
+ }
+
+ [HarmonyPatch(typeof(Aoe), nameof(Aoe.OnHit))]
+ internal static class HitSource_Aoe_OnHit_Patch {
+ [HarmonyPrefix]
+ private static void Prefix(Aoe __instance, out HitSource.Scope __state) {
+ __state = HitSource.Enter(__instance, __instance.m_owner, __instance.m_itemData);
+ }
+
+ [HarmonyFinalizer]
+ private static void Finalizer(HitSource.Scope __state) => HitSource.Exit(__state);
+ }
+
+ // Chain jumps are spawned from CustomFixedUpdate, not from OnHit. They normally inherit the marker by being
+ // clones of the live strike; holding the tag here as well marks them through the Setup postfix should the
+ // prefab ever point m_chainObj somewhere else. Only chaining Aoes pay for the component lookup.
+ [HarmonyPatch(typeof(Aoe), nameof(Aoe.CustomFixedUpdate))]
+ internal static class HitSource_Aoe_CustomFixedUpdate_Patch {
+ [HarmonyPrefix]
+ private static void Prefix(Aoe __instance, out HitSource.Scope __state) {
+ __state = __instance.m_chainStartChance > 0f
+ ? HitSource.Enter(__instance, null, null)
+ : new HitSource.Scope { PreviousWeapon = HitSource.FiringWeapon };
+ }
+
+ [HarmonyFinalizer]
+ private static void Finalizer(HitSource.Scope __state) => HitSource.Exit(__state);
+ }
+
+ [HarmonyPatch(typeof(Aoe), nameof(Aoe.Setup))]
+ internal static class HitSource_Aoe_Setup_Patch {
+ [HarmonyPostfix]
+ private static void Postfix(Aoe __instance) => HitSource.MarkIfBonus(__instance);
+ }
+
+ [HarmonyPatch(typeof(Projectile), nameof(Projectile.Setup))]
+ internal static class HitSource_Projectile_Setup_Patch {
+ [HarmonyPostfix]
+ private static void Postfix(Projectile __instance) => HitSource.MarkIfBonus(__instance);
+ }
+}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Helpers/MagicEffectsHelper.cs b/EpicLoot/src/Magic/MagicItemEffects/Helpers/MagicEffectsHelper.cs
index 11cf13924..c54a6bed2 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Helpers/MagicEffectsHelper.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Helpers/MagicEffectsHelper.cs
@@ -55,6 +55,15 @@ public static float GetTotalActiveMagicEffectValueForWeapon(Player player, ItemD
{
if (player != null)
{
+ if (itemData != null && !player.IsItemEquiped(itemData))
+ {
+ // The player-wide total already stands the firing weapon in for the held ones.
+ return itemData == HitSource.FiringWeapon
+ ? player.GetTotalActiveMagicEffectValue(effectType, scale)
+ : player.GetTotalActiveMagicEffectValue(effectType, scale) +
+ GetStandInAdjustment(player, itemData, effectType, scale);
+ }
+
return player.GetTotalActiveMagicEffectValue(effectType, scale, GetIgnoreWeapon(player, itemData));
}
else if (itemData.IsMagic(out var magicItem))
@@ -65,6 +74,47 @@ public static float GetTotalActiveMagicEffectValueForWeapon(Player player, ItemD
return 0;
}
+ ///
+ /// What to add to the player's equipped-gear total while a thrown weapon's projectile is landing. Vanilla
+ /// unequips a thrown weapon as it leaves the hand, so its effects had dropped out of every total by the
+ /// time it hit; for the duration of its hit it counts in place of the weapons held now, the way it
+ /// counted when it was thrown. Zero at all other times, including for an arrow (the bow stays equipped).
+ ///
+ public static float GetFiringWeaponAdjustment(Player player, string effectType, float scale)
+ {
+ ItemDrop.ItemData firing = HitSource.FiringWeapon;
+ if (firing == null || player != Player.m_localPlayer || player.IsItemEquiped(firing))
+ {
+ return 0f;
+ }
+
+ return GetStandInAdjustment(player, firing, effectType, scale);
+ }
+
+ // An unequipped weapon counted in place of the weapons held now: its own value, less theirs. Also what
+ // an inventory tooltip for an unequipped weapon should show.
+ private static float GetStandInAdjustment(Player player, ItemDrop.ItemData weapon, string effectType, float scale)
+ {
+ float adjustment = GetOwnEffectValue(weapon, effectType, scale);
+ if (IsWeapon(player.m_rightItem))
+ {
+ adjustment -= GetOwnEffectValue(player.m_rightItem, effectType, scale);
+ }
+ if (IsWeapon(player.m_leftItem))
+ {
+ adjustment -= GetOwnEffectValue(player.m_leftItem, effectType, scale);
+ }
+
+ return adjustment;
+ }
+
+ private static float GetOwnEffectValue(ItemDrop.ItemData item, string effectType, float scale)
+ {
+ return item != null && item.IsMagic(out MagicItem magicItem)
+ ? magicItem.GetTotalEffectValue(effectType, scale, includeSocketed: true)
+ : 0f;
+ }
+
public static bool HasActiveMagicEffect(Player player, ItemDrop.ItemData itemData, string effectType, out float effectValue)
{
return HasActiveMagicEffect(player, itemData.GetMagicItem(), effectType, out effectValue);
@@ -90,7 +140,8 @@ public static bool HasActiveMagicEffectOnWeapon(Player player, ItemDrop.ItemData
effectValue = 0f;
if (player != null)
{
- return player.HasActiveMagicEffect(effectType, out effectValue, scale, GetIgnoreWeapon(player, itemData));
+ effectValue = GetTotalActiveMagicEffectValueForWeapon(player, itemData, effectType, scale);
+ return effectValue != 0f;
}
else if (itemData.IsMagic(out var magicItem))
{
@@ -140,13 +191,55 @@ public static float GetLocalPercent(string effectType, float scale = 0.01f)
}
///
- /// The weapon that produced the current attack: the in-progress melee attack's weapon when one is
- /// active (), otherwise the player's currently equipped
- /// weapon. Used by on-hit effects socketed into the attacking weapon.
+ /// The weapon that produced the current attack: the weapon that fired the projectile or area effect
+ /// now dealing damage (), else the in-progress melee attack's weapon
+ /// (), otherwise the player's currently equipped weapon. Used by
+ /// on-hit effects socketed into the attacking weapon.
///
public static ItemDrop.ItemData GetActiveWeapon(Player player)
{
- return Attack_Patch.ActiveAttack?.m_weapon ?? player?.GetCurrentWeapon();
+ return HitSource.FiringWeapon ?? Attack_Patch.ActiveAttack?.m_weapon ?? player?.GetCurrentWeapon();
+ }
+
+ ///
+ /// Whether killed , for a Character.Damage postfix on the
+ /// attacker's client. When this client owns the target, Character.Damage has already run RPC_Damage
+ /// synchronously (ZRoutedRpc handles a call to its own peer immediately), so the health is post-hit and
+ /// is read as-is -- subtracting the hit again refunded Wager on hits that left the target alive. A
+ /// remote target has not taken the hit yet, so its owner's resistance, armor and difficulty scaling are
+ /// estimated here; fire, poison and spirit arrive as damage over time and never kill on the hit itself.
+ ///
+ public static bool IsLethalHit(Character target, HitData hit)
+ {
+ if (target == null || hit == null || target.m_nview == null || !target.m_nview.IsValid())
+ {
+ return false;
+ }
+
+ if (target.m_nview.IsOwner())
+ {
+ return target.GetHealth() <= 0f || target.IsDead();
+ }
+
+ HitData estimate = hit.Clone();
+ estimate.ApplyResistance(target.GetDamageModifiers(), out _);
+ if (!target.IsPlayer())
+ {
+ if (target.IsStaggering())
+ {
+ estimate.ApplyModifier(2f);
+ }
+ if (Game.m_worldLevel > 0)
+ {
+ estimate.ApplyArmor(Game.m_worldLevel * Game.instance.m_worldLevelEnemyBaseAC);
+ }
+ estimate.ApplyModifier(Game.instance.GetDifficultyDamageScaleEnemy(target.transform.position));
+ estimate.ApplyModifier(Game.m_playerDamageRate);
+ }
+
+ HitData.DamageTypes damage = estimate.m_damage;
+ float instantDamage = damage.GetTotalDamage() - damage.m_fire - damage.m_poison - damage.m_spirit;
+ return target.GetHealth() - instantDamage <= 0f;
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterDamagePatch.cs b/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterDamagePatch.cs
index 50e9e54c1..d30f18d4b 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterDamagePatch.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterDamagePatch.cs
@@ -15,26 +15,48 @@ namespace EpicLoot.src.Magic.MagicItemEffects.Helpers {
// apply an effect to the TARGET (Slow, Paralyze); those check the local player's effect here (attacker
// side) but apply it via an RPC to the target's owner, where movement/AI is authoritative.
//
+ // Two kinds of hit skip handlers:
+ // * A self-inflicted hit (Blood Block's cost) triggers none: it is a price, not an attack, so it must not
+ // crit itself, pay Mercenary, lifesteal its own blood back, or Slow/Paralyze the player.
+ // * A bonus hit (HitSource.IsBonusHit -- damage EpicLoot deals on its own: Reflect, novas, the meteor,
+ // chain lightning, ...) skips the weapon-strike handlers. It keeps the ones keyed on the damage type
+ // (poison, fire, lightning) and the kill rewards.
+ //
// Each effect keeps its own guard (is-local-attacker / has-effect) inside its handler, so the order among
// Normal-priority handlers is not load-bearing. Executioner keeps its original ordering relative to other
// mods via the Priority.Last prefix below.
[HarmonyPatch(typeof(Character), nameof(Character.Damage))]
internal static class SharedCharacterDamagePatch {
+ private static bool IsSelfInflicted(Character target, Character attacker) {
+ return attacker != null && attacker == target;
+ }
+
[HarmonyPrefix]
private static void EarlyPreDamagePatch(Character __instance, HitData hit) {
if (hit == null) {
return;
}
Character attacker = hit.GetAttacker();
+ if (IsSelfInflicted(__instance, attacker)) {
+ return;
+ }
+ bool strike = !HitSource.IsBonusHit;
// Attacker side -- modify the outgoing hit before it lands.
- EitrImbueAttack.ModifyOutgoingHit(hit, attacker);
+ if (strike) {
+ EitrImbueAttack.ModifyOutgoingHit(hit, attacker);
+ }
IncreaseAllPoisonDamageDone.ModifyOutgoingHit(hit, attacker);
PoisonToTrueDamage.ModifyOutgoingHit(__instance, hit, attacker);
- Mercenary.ModifyOutgoingHit(hit, attacker);
- Wager.ModifyOutgoingHit(__instance, hit, attacker);
- ChanceDoubleDamage.ModifyOutgoingHit(hit, attacker);
- ChanceToCritOnHit.ModifyOutgoingHit(hit, attacker);
+ if (strike) {
+ Mercenary.ModifyOutgoingHit(hit, attacker);
+ Wager.ModifyOutgoingHit(__instance, hit, attacker);
+ ChanceDoubleDamage.ModifyOutgoingHit(hit, attacker);
+ ChanceToCritOnHit.ModifyOutgoingHit(hit, attacker);
+ }
ModifySummonDamage.ModifyOutgoingHit(hit, attacker);
+ if (!strike) {
+ return;
+ }
// Attacker-side reads that used to live (incorrectly) on the RPC_Damage dispatcher:
// both read the local attacker's magic effects; the stagger tagger routes its write to
// the target's owner.
@@ -53,7 +75,11 @@ private static void LastPreDamagePatch(Character __instance, HitData hit) {
if (hit == null) {
return;
}
- ExecutionerCheckDamage_Character_Damage_Patch.ModifyOutgoingHit(__instance, hit);
+ if (!HitSource.IsBonusHit && !IsSelfInflicted(__instance, hit.GetAttacker())) {
+ ExecutionerCheckDamage_Character_Damage_Patch.ModifyOutgoingHit(__instance, hit);
+ }
+ // Last, so it sees fire/lightning that any earlier prefix converted into the hit.
+ ElementalHitVariant.ModifyOutgoingHit(hit, hit.GetAttacker());
}
[HarmonyPostfix]
@@ -62,28 +88,37 @@ private static void PostDamagePatch(Character __instance, HitData hit) {
return;
}
Character attacker = hit.GetAttacker();
+ if (IsSelfInflicted(__instance, attacker)) {
+ return;
+ }
+ bool strike = !HitSource.IsBonusHit;
// Wager settles first: it consumes the stake its own prefix parked for THIS hit, and several
// handlers below (ChainLightning, MeteorSummoner) deal further damage, which re-enters this
// patch and would otherwise clear the stake before it could be refunded.
- Wager.OnDamageDealt(__instance, hit);
+ if (strike) {
+ Wager.OnDamageDealt(__instance, hit);
- // On-hit reactions (attacker side unless the handler guards otherwise).
- AddLifeSteal.CheckAndDoLifeSteal(hit, attacker);
- BloodDrinker.OnDamageDealt(hit, attacker);
- AddEitrLeech.OnDamageDealt(hit, attacker);
- ChainLightning.OnDamageDealt(__instance, hit, attacker);
- StrikeCausesLightning.OnDamageDealt(__instance, hit, attacker);
- ApplySlow_Character_Damage_Patch.OnDamageDealt(__instance, hit, attacker);
- Paralyze.OnDamaged(__instance, hit, attacker);
- StaggerOnDamageTaken_Character_Damage_Patch.OnDamageDealt(__instance, hit, attacker);
- HealthGainPerXDamageDone.OnDamageDealt(hit, attacker);
- LifeGainOnHit.OnDamageDealt(hit, attacker);
+ // On-hit reactions (attacker side unless the handler guards otherwise).
+ AddLifeSteal.CheckAndDoLifeSteal(hit, attacker);
+ BloodDrinker.OnDamageDealt(hit, attacker);
+ AddEitrLeech.OnDamageDealt(hit, attacker);
+ ChainLightning.OnDamageDealt(__instance, hit, attacker);
+ StrikeCausesLightning.OnDamageDealt(__instance, hit, attacker);
+ ApplySlow_Character_Damage_Patch.OnDamageDealt(__instance, hit, attacker);
+ Paralyze.OnDamaged(__instance, hit, attacker);
+ StaggerOnDamageTaken_Character_Damage_Patch.OnDamageDealt(__instance, hit, attacker);
+ HealthGainPerXDamageDone.OnDamageDealt(hit, attacker);
+ LifeGainOnHit.OnDamageDealt(hit, attacker);
+ }
+ // Keyed on the damage type or the kill, so a bonus hit earns these too.
GainAdrenalineWhenApplyingPoison.OnDamageDealt(__instance, hit, attacker);
BurningAdrenaline.OnDamageDealt(hit, attacker);
StaminaOnKill.OnDamageDealt(__instance, hit, attacker);
Conduit.OnDamageDealt(__instance, hit, attacker);
- EikthyrShockingCharge.OnDamageDealt(__instance, hit, attacker);
- MeteorSummoner.OnDamageDealt(__instance, hit, attacker);
+ if (strike) {
+ EikthyrShockingCharge.OnDamageDealt(__instance, hit, attacker);
+ MeteorSummoner.OnDamageDealt(__instance, hit, attacker);
+ }
QueenEverflow.OnDamageDealt(__instance, hit, attacker);
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterRpcDamagePatch.cs b/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterRpcDamagePatch.cs
index b65cf82de..885e4b1e5 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterRpcDamagePatch.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Helpers/SharedCharacterRpcDamagePatch.cs
@@ -10,25 +10,34 @@ namespace EpicLoot.src.Magic.MagicItemEffects.Helpers {
// * The prefix runs the incoming-hit modifiers in a fixed order and can cancel the hit entirely
// (AvoidDamageTaken). The one load-bearing ordering constraint from the old code is preserved by
// call order: IncomingPhysicalConversion moves physical damage onto an element BEFORE
- // ModifyResistance reduces it (previously done with Priority.High). Avoidance is checked before the
- // resource-spending mitigations (EitrShield, Coinplated) so a fully-avoided hit never spends eitr or
- // coins. EitrShield runs first of those two, so eitr is drained before the purse.
- // * The postfix runs the on-damage-taken reactions (slow, adrenaline, boss retributions). These run
- // even when the prefix cancels the hit -- matching the old behavior, where these were independent
- // postfixes that Harmony always executes.
+ // ModifyResistance reduces it (previously done with Priority.High).
+ // * The postfix runs the on-damage-taken reactions (slow, adrenaline, boss retributions), for hits that
+ // landed: not for one AvoidDamageTaken cancelled, nor one vanilla discards itself.
+ // * The resource-spending mitigations (EitrShield, Coinplated) and AutoMeads are not here: they run from
+ // SharedPlayerPostArmorDamagePatch below, once the hit has been through the bubble, block and armor.
+ //
+ // The prefix runs before vanilla's own checks, so it first asks whether vanilla will discard the hit
+ // (WillLand) -- otherwise a dodged hit, one from a despawned attacker or a PvP-off hit still spent
+ // resources and triggered reactions. A self-inflicted hit (Blood Block's cost) is a price, not an attack,
+ // so it triggers nothing here either.
//
// Each effect keeps its own guard (is-local-victim / has-effect / attacker checks) inside its handler.
[HarmonyPatch(typeof(Character), nameof(Character.RPC_Damage))]
internal static class SharedCharacterRpcDamagePatch {
- // Returns false to cancel RPC_Damage (AvoidDamageTaken rolled an avoid).
+ // Returns false to cancel RPC_Damage (AvoidDamageTaken rolled an avoid). __state tells the postfix
+ // whether the hit landed; it is per call, so a re-entrant RPC_Damage (ReflectDamage) can't clobber it.
[HarmonyPrefix]
- private static bool Prefix(Character __instance, HitData hit) {
+ private static bool Prefix(Character __instance, HitData hit, out bool __state) {
+ __state = false;
if (hit == null) {
return true;
}
// Resolved once per hit; a local (not a static) so a re-entrant RPC_Damage from a handler
// below (e.g. ReflectDamage) can never clobber an outer invocation's attacker.
Character attacker = hit.GetAttacker();
+ if (!WillLand(__instance, hit, attacker) || IsSelfInflicted(__instance, hit, attacker)) {
+ return true;
+ }
// NOTE: Opportunist and the melee stagger-duration tagger moved to the attacker-side
// dispatcher (SharedCharacterDamagePatch): they read the ATTACKER's magic effects, which
@@ -38,36 +47,85 @@ private static bool Prefix(Character __instance, HitData hit) {
// Convert physical -> element first, so the resistance step below reduces the converted damage.
IncomingPhysicalConversion.ModifyIncoming(__instance, hit);
- // Avoidance is decided before eitr is spent; an avoided hit cancels the whole method.
+ // Avoidance is decided before anything is spent; an avoided hit cancels the whole method.
if (!AvoidDamageTaken_Character_RPC_Damage_Patch.ShouldTakeDamage(__instance, hit, attacker)) {
return false;
}
ModifyResistance.ModifyIncoming(__instance, hit);
- EitrShield.ModifyIncoming(__instance, hit);
- Coinplated.ModifyIncoming(__instance, hit);
DamageReductionAtNight.ModifyIncoming(__instance, hit);
// Victim-side reactions moved off Character.Damage so they fire on the victim's own client
// regardless of who owns the attacker. Run after the avoid check so an avoided hit doesn't
- // waste a mead or reflect. ReflectDamage runs last (it reflects the fully-mitigated hit).
+ // waste a reflect. ReflectDamage runs last (it reflects the hit after these reductions).
OffSetAttack.ReduceIncomingHit(__instance, hit);
- AutoMeads.OnIncomingHit(__instance, hit);
ReflectiveDamage_Character_Damage_Patch.OnIncomingHit(__instance, hit);
+ __state = true;
return true;
}
[HarmonyPostfix]
- private static void Postfix(Character __instance, HitData hit) {
- if (hit == null) {
+ private static void Postfix(Character __instance, HitData hit, bool __state) {
+ if (hit == null || !__state) {
return;
}
// On-damage-taken reactions.
DamageTakenGivesAdrenaline.OnDamageTaken(__instance, hit);
Bloodrage.OnDamageTaken(__instance, hit);
- ElderForestsAid.OnDamageTaken(__instance, hit);
- ModerIcyRetribution.OnDamageTaken(__instance, hit);
+ // Retaliation needs someone to retaliate against: not a fall, lava, drowning or the like.
+ if (hit.GetAttacker() != null) {
+ ElderForestsAid.OnDamageTaken(__instance, hit);
+ ModerIcyRetribution.OnDamageTaken(__instance, hit);
+ }
+ }
+
+ // Mirrors the early returns at the top of vanilla Character.RPC_Damage: debug flying, not the owner,
+ // dead/teleporting/in a cutscene, dodge invincibility, an attacker that no longer exists, and a player
+ // hit by a player with PvP off. Keep in step with vanilla.
+ private static bool WillLand(Character target, HitData hit, Character attacker) {
+ if (target.IsDebugFlying() || target.m_nview == null || !target.m_nview.IsOwner()) {
+ return false;
+ }
+ if (target.GetHealth() <= 0f || target.IsDead() || target.IsTeleporting() || target.InCutscene() ||
+ (hit.m_dodgeable && target.IsDodgeInvincible())) {
+ return false;
+ }
+ if (hit.HaveAttacker() && attacker == null) {
+ return false;
+ }
+ return !(target.IsPlayer() && !target.IsPVPEnabled() && attacker != null && attacker.IsPlayer() &&
+ !hit.m_ignorePVP);
+ }
+
+ internal static bool IsSelfInflicted(Character target, HitData hit, Character attacker) {
+ return hit.m_hitType == HitData.HitType.Self || (attacker != null && attacker == target);
+ }
+ }
+
+ // The resource-spending mitigations, run where the hit is final: Player.DamageArmorDurability is called
+ // from exactly one place, in Character.RPC_Damage right after ApplyArmor, and only for players. By then
+ // vanilla has applied its discard checks, difficulty scaling, the Staff of Protection bubble, the block,
+ // resistances and armor, so eitr and coins are only spent on damage that would otherwise get through --
+ // they used to be charged on the raw hit, before all of that. Fire, poison and spirit are still in the hit
+ // here (vanilla splits them off into damage over time next), so the shields cover those as before.
+ //
+ // A postfix rather than a prefix, so another mod's skipping prefix on this method can't skip these.
+ // AvoidDamageTaken cancels RPC_Damage outright, so an avoided hit never gets here.
+ [HarmonyPatch(typeof(Player), nameof(Player.DamageArmorDurability))]
+ internal static class SharedPlayerPostArmorDamagePatch {
+ [HarmonyPostfix]
+ private static void Postfix(Player __instance, HitData hit) {
+ if (hit == null || __instance != Player.m_localPlayer ||
+ SharedCharacterRpcDamagePatch.IsSelfInflicted(__instance, hit, hit.GetAttacker())) {
+ return;
+ }
+
+ // EitrShield first, so eitr is drained before the purse.
+ EitrShield.ModifyIncoming(__instance, hit);
+ Coinplated.ModifyIncoming(__instance, hit);
+ // Last: it judges the damage that will actually land.
+ AutoMeads.OnIncomingHit(__instance, hit);
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Immovable.cs b/EpicLoot/src/Magic/MagicItemEffects/Immovable.cs
index 007b7aa85..edc2d250e 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/Immovable.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Immovable.cs
@@ -16,6 +16,27 @@ private static bool Prefix(Character __instance)
}
}
+ // Skipping Stagger alone left the guard broken: vanilla BlockAttack asks AddStaggerDamage whether the block
+ // staggered, and a full stagger bar answers yes -- so the block let the whole hit through, with no stagger
+ // to show for it, and the bar stayed full (re-pinned by every hit) until it drained. While blocking with
+ // Immovable, stagger damage doesn't build at all. Stamina still runs out as usual.
+ [HarmonyPatch(typeof(Character), nameof(Character.AddStaggerDamage))]
+ public class Immovable_Character_AddStaggerDamage_Patch
+ {
+ [UsedImplicitly]
+ private static bool Prefix(Character __instance, ref bool __result)
+ {
+ if (__instance is Player player && player.IsBlocking() &&
+ player.HasActiveMagicEffect(MagicEffectType.Immovable, out float _))
+ {
+ __result = false;
+ return false;
+ }
+
+ return true;
+ }
+ }
+
[HarmonyPatch(typeof(Character), nameof(Character.ApplyPushback), typeof(Vector3), typeof(float))]
public class Immovable_Character_ApplyPushback_Patch
{
diff --git a/EpicLoot/src/Magic/MagicItemEffects/ModifyMeads.cs b/EpicLoot/src/Magic/MagicItemEffects/ModifyMeads.cs
index e05668a4b..9e9d72d3c 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/ModifyMeads.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/ModifyMeads.cs
@@ -47,7 +47,7 @@ dbEffect is not SE_Stats seStats ||
SE_Stats returnEffect = seStats;
if (Player.m_localPlayer.HasActiveMagicEffect(MagicEffectType.InstantMead) &&
- ModifyWithLowHealth.PlayerHasLowHealth(Player.m_localPlayer))
+ (ModifyWithLowHealth.PlayerHasLowHealth(Player.m_localPlayer) || AutoMeads.DrinkingForCriticalHit))
{
returnEffect = TryCreateInstantMead(returnEffect);
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/ModifyWithLowHealth.cs b/EpicLoot/src/Magic/MagicItemEffects/ModifyWithLowHealth.cs
index 7c9b4c816..9c8485ac1 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/ModifyWithLowHealth.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/ModifyWithLowHealth.cs
@@ -58,6 +58,10 @@ public static float GetLowHealthPercentage(Player player)
return lowHealthThreshold;
}
+ // Expects the hit as it stands after the block, resistances and armor (see
+ // SharedPlayerPostArmorDamagePatch), so it only applies what vanilla still does to it before the damage
+ // lands: fire, poison and spirit are split off into damage over time, the rest is scaled by the world's
+ // damage-taken rate, and a creature's world-level bonus is added flat (HitData.GetTotalDamage).
public static bool PlayerWillBecomeHealthCritical(Player player, HitData hit)
{
if (PlayerHasLowHealth(player))
@@ -67,17 +71,13 @@ public static bool PlayerWillBecomeHealthCritical(Player player, HitData hit)
float lowHealthPercentage = Mathf.Min(ModifyWithLowHealth.GetLowHealthPercentage(player), 1.0f) * player.GetMaxHealth();
float currentHealth = player.GetHealth();
- float hitTotalDamage = hit.m_damage.EpicLootGetTotalDamageAgainstPlayer();
- float armorValue = player.GetBodyArmor();
- hitTotalDamage = HitData.DamageTypes.ApplyArmor(hitTotalDamage, armorValue);
+ HitData.DamageTypes damage = hit.m_damage;
+ float typedDamage = damage.GetTotalDamage();
+ float instantDamage = (typedDamage - damage.m_fire - damage.m_poison - damage.m_spirit) * Game.m_localDamgeTakenRate +
+ (hit.GetTotalDamage() - typedDamage);
- if ((currentHealth - hitTotalDamage) < lowHealthPercentage)
- {
- return true;
- }
-
- return false;
+ return currentHealth - instantDamage < lowHealthPercentage;
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/MultiShot.cs b/EpicLoot/src/Magic/MagicItemEffects/MultiShot.cs
index 6004a32ee..aa32e2c3a 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/MultiShot.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/MultiShot.cs
@@ -163,10 +163,13 @@ private static bool ModifyShot(ref Player player, ref Attack attack, Dictionary<
}
///
- /// Restore the attack damages to previous state if changed by the prefix.
+ /// Restore the attack damages to previous state if changed by the prefix. A finalizer, not a
+ /// postfix: m_shared is shared by every weapon of that kind, so an exception mid-burst would
+ /// otherwise leave all of them at the reduced damage until the game restarts.
///
[HarmonyPatch(typeof(Attack), nameof(Attack.FireProjectileBurst))]
- public static void Postfix(Attack __instance, ref HitData.DamageTypes? __state)
+ [HarmonyFinalizer]
+ public static void Attack_FireProjectileBurst_Finalizer(Attack __instance, HitData.DamageTypes? __state)
{
if (__state != null)
{
diff --git a/EpicLoot/src/Magic/MagicItemEffects/ReflectDamage.cs b/EpicLoot/src/Magic/MagicItemEffects/ReflectDamage.cs
index 588d76928..f6233ba3a 100755
--- a/EpicLoot/src/Magic/MagicItemEffects/ReflectDamage.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/ReflectDamage.cs
@@ -1,11 +1,14 @@
-namespace EpicLoot.MagicItemEffects
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
+
+namespace EpicLoot.MagicItemEffects
{
public class ReflectiveDamage_Character_Damage_Patch
{
private static bool _isApplyingReflectiveDmg;
- // Prefix handler invoked by CharacterDamageDispatch (victim-side, runs at Priority.Last so it
- // reflects the fully-modified incoming damage).
+ // Prefix handler invoked by SharedCharacterRpcDamagePatch (victim-side, runs last in that prefix so
+ // it reflects the hit after the conversion, resistance and night reductions). The reflected hit is
+ // a bonus hit: it gets none of the local player's weapon-strike effects.
public static void OnIncomingHit(Character __instance, HitData hit)
{
var attacker = hit.GetAttacker();
@@ -26,7 +29,7 @@ public static void OnIncomingHit(Character __instance, HitData hit)
try
{
_isApplyingReflectiveDmg = true;
- attacker.Damage(hitData);
+ HitSource.DealBonusDamage(attacker, hitData);
}
finally
{
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/BonemassCorpseRot.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/BonemassCorpseRot.cs
index e8a741248..bee0f0e22 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/BonemassCorpseRot.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/BonemassCorpseRot.cs
@@ -24,6 +24,16 @@ public static class BonemassCorpseRot {
{ PoisonPerTierKey, DefaultPoisonPerTier },
};
+ // Tooltip: "Corpse Rot: {0} Poison" -- the same value * PoisonPerTier the burst deals, so the
+ // shown number rises with the shard's rarity instead of the effect reading as a flat name.
+ public static void RegisterDisplayValues() {
+ MagicItem.RegisterDisplayValues(MagicEffectType.CorpseRot,
+ value => new object[] {
+ value * EffectConfig.Get(MagicEffectType.CorpseRot,
+ PoisonPerTierKey, DefaultPoisonPerTier),
+ });
+ }
+
// Floored just above zero: a ttl of 0 is "no timeout" to vanilla, which would gate the shard
// permanently rather than removing the cooldown.
private static float GetCooldown() {
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/Coinplated.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/Coinplated.cs
index 9a8b3c62c..1f6a7da9e 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/Coinplated.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/Coinplated.cs
@@ -32,8 +32,8 @@ public static void RegisterDisplayValues() {
});
}
- // Prefix handler invoked by CharacterRpcDamageDispatch (victim-side incoming modifier; runs after
- // avoidance so a fully-avoided hit never spends coins).
+ // Invoked by SharedPlayerPostArmorDamagePatch (victim-side): after the bubble, block, resistances and
+ // armor, so coins are only spent on damage that would actually land.
public static void ModifyIncoming(Character __instance, HitData hit) {
if (hit == null || __instance != Player.m_localPlayer) {
return;
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/DamageConversionEffects.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/DamageConversionEffects.cs
index 222b608f1..6295f7538 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/DamageConversionEffects.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/DamageConversionEffects.cs
@@ -1,9 +1,17 @@
-namespace EpicLoot.MagicItemEffects.Shards {
+using UnityEngine;
+
+namespace EpicLoot.MagicItemEffects.Shards {
// Provides a conversion of incoming physical damage into elemental damage based on the player's active magic effects.
public static class IncomingPhysicalConversion {
// Prefix handler invoked by CharacterRpcDamageDispatch (victim-side incoming modifier). The
// dispatcher calls this before ModifyResistance so the converted element is then reduced by the
- // player's matching percentage resistance
+ // player's matching percentage resistance.
+ //
+ // With IsBlocked (from the BlockAttack postfix) it converts the on-block shares only. The general
+ // shares were already converted by the RPC_Damage prefix, which runs before vanilla's BlockAttack on
+ // this same HitData; adding them again converted a blocked hit twice (30% became 51%). The on-block
+ // shares are meant as a share of the original physical damage, of which only (1 - general) is left
+ // here, so they are scaled up to match.
public static void ModifyIncoming(Character __instance, HitData hit, bool IsBlocked = false, bool IsParry = false) {
if (__instance != Player.m_localPlayer) {
return;
@@ -16,10 +24,15 @@ public static void ModifyIncoming(Character __instance, HitData hit, bool IsBloc
float toLightning = player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToLightning, 0.01f);
if (IsBlocked) {
- toFire += player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToFireOnBlock, 0.01f);
- toFrost += player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToFrostOnBlock, 0.01f);
- toPoison += player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToPoisonOnBlock, 0.01f);
- toLightning += player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToLightningOnBlock, 0.01f);
+ float remaining = 1f - Mathf.Min(1f, toFire + toFrost + toPoison + toLightning);
+ if (remaining <= 0f) {
+ return;
+ }
+
+ toFire = player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToFireOnBlock, 0.01f) / remaining;
+ toFrost = player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToFrostOnBlock, 0.01f) / remaining;
+ toPoison = player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToPoisonOnBlock, 0.01f) / remaining;
+ toLightning = player.GetTotalActiveMagicEffectValue(MagicEffectType.PhysToLightningOnBlock, 0.01f) / remaining;
}
float total = toFire + toFrost + toPoison + toLightning;
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/EikthyrShockingCharge.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/EikthyrShockingCharge.cs
index 15eb6c430..a2540d5ea 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/EikthyrShockingCharge.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/EikthyrShockingCharge.cs
@@ -7,18 +7,26 @@ namespace EpicLoot.MagicItemEffects.Shards {
public static class EikthyrShockingCharge {
// All tunable in this effect's Config block in config/shardstones.json, under these key names.
//
- // Hits required to trigger a discharge, and the portion of the banked combat damage the shockwave
- // delivers. Cone geometry: reach straight ahead, and the full width of the cone at that reach --
- // the half-width grows linearly from 0 at the player to (ConeMaxWidth / 2) at ConeLength. The
- // ignore window is how long after a discharge the shockwave's own hits are kept from rebuilding
- // the charge.
- public const int DefaultMaxCharges = 15;
+ // Hits required to trigger a discharge: MaxCharges is the threshold at a shard value of zero,
+ // ChargesPerValue is how many hits each point of value shaves off it, and MinCharges is the floor
+ // it may never drop below. This is what the shard's rarity buys -- at the shipped ramp a Rare
+ // Eikthyr shard discharges every 15 hits and an Ancient one every 9.
+ //
+ // DamageFraction is the portion of the banked combat damage the shockwave delivers. Cone geometry:
+ // reach straight ahead, and the full width of the cone at that reach -- the half-width grows
+ // linearly from 0 at the player to (ConeMaxWidth / 2) at ConeLength. The ignore window is how long
+ // after a discharge the shockwave's own hits are kept from rebuilding the charge.
+ public const int DefaultMaxCharges = 18;
+ public const float DefaultChargesPerValue = 0.3f;
+ public const int DefaultMinCharges = 6;
public const float DefaultDamageFraction = 0.3f;
public const float DefaultConeLength = 4f;
public const float DefaultConeMaxWidth = 4f;
public const float DefaultDischargeIgnoreWindow = 0.3f;
private const string MaxChargesKey = "MaxCharges";
+ private const string ChargesPerValueKey = "ChargesPerValue";
+ private const string MinChargesKey = "MinCharges";
private const string DamageFractionKey = "DamageFraction";
private const string ConeLengthKey = "ConeLength";
private const string ConeMaxWidthKey = "ConeMaxWidth";
@@ -26,16 +34,37 @@ public static class EikthyrShockingCharge {
public static readonly Dictionary DefaultConfig = new Dictionary {
{ MaxChargesKey, DefaultMaxCharges },
+ { ChargesPerValueKey, DefaultChargesPerValue },
+ { MinChargesKey, DefaultMinCharges },
{ DamageFractionKey, DefaultDamageFraction },
{ ConeLengthKey, DefaultConeLength },
{ ConeMaxWidthKey, DefaultConeMaxWidth },
{ DischargeIgnoreWindowKey, DefaultDischargeIgnoreWindow },
};
- // Clamped to at least 1 so a misconfiguration can't discharge on every hit through a zero threshold.
- private static int GetMaxCharges() {
- return EffectConfig.GetIntAtLeast(MagicEffectType.ShockingCharge,
+ // Hits needed to discharge at the given shard value. Rounded rather than truncated so a fractional
+ // rate lands on the nearer threshold, then clamped at both ends: never above the configured ceiling
+ // (so a negative rate cannot make a shard worse than no shard) and never below MinCharges or 1, so
+ // a misconfiguration can only weaken the effect rather than turn every hit into a discharge.
+ private static int GetChargesNeeded(float value) {
+ var ceiling = EffectConfig.GetIntAtLeast(MagicEffectType.ShockingCharge,
MaxChargesKey, DefaultMaxCharges, 1);
+ var perValue = EffectConfig.Get(MagicEffectType.ShockingCharge,
+ ChargesPerValueKey, DefaultChargesPerValue);
+ var floor = Mathf.Clamp(EffectConfig.GetIntAtLeast(MagicEffectType.ShockingCharge,
+ MinChargesKey, DefaultMinCharges, 1), 1, ceiling);
+ return Mathf.Clamp(Mathf.RoundToInt(ceiling - value * perValue), floor, ceiling);
+ }
+
+ // Tooltip: "Shocking Charge: Every {0} Hits", with {1} the discharge's share of the banked damage.
+ // Both are derived from the same helpers the effect runs on, so a retune moves the shown text too.
+ public static void RegisterDisplayValues() {
+ MagicItem.RegisterDisplayValues(MagicEffectType.ShockingCharge,
+ value => new object[] {
+ (float)GetChargesNeeded(value),
+ EffectConfig.Get(MagicEffectType.ShockingCharge,
+ DamageFractionKey, DefaultDamageFraction) * 100f,
+ });
}
// Floored above zero: the cone test divides by the reach.
@@ -58,7 +87,11 @@ private static float GetConeLength() {
private static bool _indicatorMissingLogged;
// Live charge state, read by SE_ShockingChargeIndicator for its icon text and removal check.
public static int CurrentCharges => _charges;
- public static int MaxChargeCount => GetMaxCharges();
+ // The HUD indicator's denominator: the threshold for the value the player is actually carrying,
+ // not the configured ceiling, so the counter matches what it takes to fire.
+ public static int MaxChargeCount => GetChargesNeeded(Player.m_localPlayer != null
+ ? Player.m_localPlayer.GetTotalActiveMagicEffectValue(MagicEffectType.ShockingCharge, 1f)
+ : 0f);
// Postfix handler invoked by CharacterDamageDispatch (on-hit reaction).
public static void OnDamageDealt(Character __instance, HitData hit, Character attacker) {
@@ -91,7 +124,7 @@ public static void OnDamageDealt(Character __instance, HitData hit, Character at
_bankedDamage += contributed;
- if (++_charges < GetMaxCharges()) {
+ if (++_charges < GetChargesNeeded(value)) {
ShowIndicator(player);
return;
}
@@ -176,7 +209,7 @@ private static void DamageEnemiesInCone(Player player, Vector3 origin, Vector3 d
};
hit.m_damage.m_lightning = lightningDamage;
hit.SetAttacker(player);
- character.Damage(hit);
+ HitSource.DealBonusDamage(character, hit);
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/EitrShield.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/EitrShield.cs
index 172469795..cbd181bcb 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/EitrShield.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/EitrShield.cs
@@ -6,8 +6,8 @@ namespace EpicLoot.MagicItemEffects.Shards {
// Absorbs a portion of incoming damage by spending Eitr
public static class EitrShield {
static GameObject effect = null;
- // Prefix handler invoked by CharacterRpcDamageDispatch (victim-side incoming modifier; runs after
- // avoidance so a fully-avoided hit never spends eitr).
+ // Invoked by SharedPlayerPostArmorDamagePatch (victim-side): after the bubble, block, resistances and
+ // armor, so eitr is only spent on damage that would actually land.
public static void ModifyIncoming(Character __instance, HitData hit) {
if (hit == null || __instance != Player.m_localPlayer) {
return;
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/ElderForestsAid.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/ElderForestsAid.cs
index 439f1cd24..7bb1d7b17 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/ElderForestsAid.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/ElderForestsAid.cs
@@ -7,26 +7,53 @@ namespace EpicLoot.MagicItemEffects.Shards {
// When the player takes damage, immobilizes nearby enemies for a short duration. The radius and cooldown scale with the shard value.
public static class ElderForestsAid {
// Both the radius and the cooldown scale with the shard value: radius is BaseRadius plus
- // RadiusPerTier per point, cooldown is Cooldown plus CooldownPerValue per point -- so a stronger
- // shard reaches further but fires less often. All tunable in this effect's Config block in
- // config/shardstones.json, under these key names.
- public const float DefaultCooldown = 30f;
+ // RadiusPerTier per point, cooldown is Cooldown MINUS CooldownPerValue per point, floored at
+ // MinCooldown -- so a stronger shard reaches further AND fires more often. At the shipped ramp
+ // that is 7.5m / 45s at Rare through 10.5m / 15s at Ancient. All tunable in this effect's Config
+ // block in config/shardstones.json, under these key names.
+ public const float DefaultCooldown = 60f;
public const float DefaultCooldownPerValue = 1.5f;
+ public const float DefaultMinCooldown = 10f;
public const float DefaultBaseRadius = 6f;
public const float DefaultRadiusPerTier = 0.15f;
private const string CooldownKey = "Cooldown";
private const string CooldownPerValueKey = "CooldownPerValue";
+ private const string MinCooldownKey = "MinCooldown";
private const string BaseRadiusKey = "BaseRadius";
private const string RadiusPerTierKey = "RadiusPerTier";
public static readonly Dictionary DefaultConfig = new Dictionary {
{ CooldownKey, DefaultCooldown },
{ CooldownPerValueKey, DefaultCooldownPerValue },
+ { MinCooldownKey, DefaultMinCooldown },
{ BaseRadiusKey, DefaultBaseRadius },
{ RadiusPerTierKey, DefaultRadiusPerTier },
};
+ private static float GetRadius(float value) {
+ return EffectConfig.Get(MagicEffectType.ForestsAid, BaseRadiusKey, DefaultBaseRadius)
+ + value * EffectConfig.Get(MagicEffectType.ForestsAid, RadiusPerTierKey, DefaultRadiusPerTier);
+ }
+
+ // Shrinks as the shard value grows, never past MinCooldown. Floored just above zero on top of
+ // that, because a ttl of 0 is "no timeout" to vanilla, which would gate the shard permanently
+ // rather than removing the cooldown.
+ private static float GetCooldown(float value) {
+ var cooldown = EffectConfig.Get(MagicEffectType.ForestsAid, CooldownKey, DefaultCooldown)
+ - value * EffectConfig.Get(MagicEffectType.ForestsAid,
+ CooldownPerValueKey, DefaultCooldownPerValue);
+ var floor = EffectConfig.Get(MagicEffectType.ForestsAid, MinCooldownKey, DefaultMinCooldown);
+ return Mathf.Max(0.1f, Mathf.Max(floor, cooldown));
+ }
+
+ // Tooltip: "Forest's Aid: {0}m, {1}s Cooldown" -- both read through the same helpers the effect
+ // runs on, so the shown numbers follow a retune and visibly improve with the shard's rarity.
+ public static void RegisterDisplayValues() {
+ MagicItem.RegisterDisplayValues(MagicEffectType.ForestsAid,
+ value => new object[] { GetRadius(value), GetCooldown(value) });
+ }
+
private const string ImmobilizeSE = "ImmobilizedAshlands";
private const string HitFxPrefab = "fx_natureweapon_hit";
private static readonly int ImmobilizeHash = ImmobilizeSE.GetStableHashCode();
@@ -55,17 +82,8 @@ public static void OnDamageTaken(Character __instance, HitData hit) {
return;
}
- var radius = EffectConfig.Get(MagicEffectType.ForestsAid, BaseRadiusKey, DefaultBaseRadius)
- + value * EffectConfig.Get(MagicEffectType.ForestsAid, RadiusPerTierKey, DefaultRadiusPerTier);
- // Cooldown floored just above zero: a ttl of 0 is "no timeout" to vanilla, which would gate the
- // shard permanently rather than removing the cooldown.
- var cooldown = Mathf.Max(0.1f,
- EffectConfig.Get(MagicEffectType.ForestsAid, CooldownKey, DefaultCooldown)
- + value * EffectConfig.Get(MagicEffectType.ForestsAid,
- CooldownPerValueKey, DefaultCooldownPerValue));
-
- Immobilize(player, radius);
- ShowCooldown(player, cooldown);
+ Immobilize(player, GetRadius(value));
+ ShowCooldown(player, GetCooldown(value));
}
private static void Immobilize(Player player, float radius) {
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/FaderNecroticFire.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/FaderNecroticFire.cs
index 2e97e1eb4..200cbb783 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/FaderNecroticFire.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/FaderNecroticFire.cs
@@ -3,6 +3,13 @@
namespace EpicLoot.MagicItemEffects.Shards {
// Provides a conversion of physical damage to fire and poison damage based on the player's Necrotic Fire effect value.
public static class NecroticFire {
+ // Tooltip: "Necrotic Fire: +{0}% Fire and Poison". The shard value is a fraction of the stripped
+ // physical pool paid out as EACH element, so it reads as a percentage rather than a bare 0.2.
+ public static void RegisterDisplayValues() {
+ MagicItem.RegisterDisplayValues(MagicEffectType.NecroticFire,
+ value => new object[] { value * 100f });
+ }
+
// GetDamage postfix handler invoked by ModifyDamage (per-weapon modifier).
public static void ModifyWeaponDamage(ItemDrop.ItemData __instance, ref HitData.DamageTypes __result) {
// Only when the local player has this weapon equipped (also gates the weapon tooltip).
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/MeteorSummoner.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/MeteorSummoner.cs
index 9f07dcb96..fde91964c 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/MeteorSummoner.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/MeteorSummoner.cs
@@ -131,6 +131,9 @@ private static void SummonMeteor(Player player, Character target, float fireDama
var velocity = (targetPos - spawnPos).normalized * speed;
var meteor = Object.Instantiate(prefab, spawnPos, Quaternion.LookRotation(velocity));
+ // A bonus hit, not a weapon strike: no crits, stakes or on-hit procs on the meteor's damage (or on
+ // whatever it spawns on impact).
+ HitSource.MarkBonusSource(meteor);
var projectile = meteor.GetComponent();
if (projectile == null) {
return;
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/ModerIcyRetribution.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/ModerIcyRetribution.cs
index 591ff1ece..2b8b7f07f 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/ModerIcyRetribution.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/ModerIcyRetribution.cs
@@ -7,22 +7,36 @@ namespace EpicLoot.MagicItemEffects.Shards {
public static class ModerIcyRetribution {
// All tunable in this effect's Config block in config/shardstones.json, under these key names.
public const float DefaultRadius = 8f;
- public const float DefaultFrostPerTier = 8f; // frost damage per point of value (15..25 -> 120..200)
- public const float DefaultBaseCooldown = 140f; // cooldown at Epic (the shard's rarity floor)
- public const float DefaultCooldownPerRarity = 20f; // added per rarity above Epic
+ public const float DefaultFrostPerTier = 8f; // frost damage per point of value (15..30 -> 120..240)
+ public const float DefaultBaseCooldown = 200f; // cooldown at Epic (the shard's rarity floor)
+ public const float DefaultCooldownPerRarity = 20f; // removed per rarity above Epic
+ public const float DefaultMinCooldown = 30f; // floor, so a retune cannot make it near-instant
private const string RadiusKey = "Radius";
private const string FrostPerTierKey = "FrostPerTier";
private const string BaseCooldownKey = "BaseCooldown";
private const string CooldownPerRarityKey = "CooldownPerRarity";
+ private const string MinCooldownKey = "MinCooldown";
public static readonly Dictionary DefaultConfig = new Dictionary {
{ RadiusKey, DefaultRadius },
{ FrostPerTierKey, DefaultFrostPerTier },
{ BaseCooldownKey, DefaultBaseCooldown },
{ CooldownPerRarityKey, DefaultCooldownPerRarity },
+ { MinCooldownKey, DefaultMinCooldown },
};
+ // Tooltip: "Icy Retribution: {0} Frost". The cooldown is keyed off the shard's rarity rather than
+ // its value (see GetCooldown), and a provider only receives the value -- summing two socketed
+ // shards would misreport it -- so the cooldown stays in the Shift-detail config lines instead.
+ public static void RegisterDisplayValues() {
+ MagicItem.RegisterDisplayValues(MagicEffectType.IcyRetribution,
+ value => new object[] {
+ value * EffectConfig.Get(MagicEffectType.IcyRetribution,
+ FrostPerTierKey, DefaultFrostPerTier),
+ });
+ }
+
// Visual: our own trimmed copy of the fenring's ice nova, built and cached by FrostNovaFx so the
// fenring's full-length nova is left untouched. Played at the helper's default speed.
private const string NovaTemplateName = "EL_ModerIcyRetributionNova";
@@ -64,16 +78,18 @@ public static void OnDamageTaken(Character __instance, HitData hit) {
ShowCooldown(player, GetCooldown(player));
}
- // Cooldown length scales with the highest rarity among the equipped IcyRetribution shards: by default
- // 140s at Epic, +20s for each rarity above it (Legendary 160s, Mythic 180s). Floored just above zero:
- // a ttl of 0 is "no timeout" to vanilla, which would gate the shard permanently.
+ // Cooldown length shortens with the highest rarity among the equipped IcyRetribution shards: by
+ // default 200s at Epic, -20s for each rarity above it (Legendary 180s, Mythic 160s, Ancient 140s),
+ // never past MinCooldown. Floored just above zero on top of that: a ttl of 0 is "no timeout" to
+ // vanilla, which would gate the shard permanently.
private static float GetCooldown(Player player) {
var stepsAboveEpic = Mathf.Max(0, (int)GetEffectRarity(player) - (int)ItemRarity.Epic);
var baseCooldown = EffectConfig.Get(MagicEffectType.IcyRetribution,
BaseCooldownKey, DefaultBaseCooldown);
var perRarity = EffectConfig.Get(MagicEffectType.IcyRetribution,
CooldownPerRarityKey, DefaultCooldownPerRarity);
- return Mathf.Max(0.1f, baseCooldown + stepsAboveEpic * perRarity);
+ var floor = EffectConfig.Get(MagicEffectType.IcyRetribution, MinCooldownKey, DefaultMinCooldown);
+ return Mathf.Max(0.1f, Mathf.Max(floor, baseCooldown - stepsAboveEpic * perRarity));
}
// Highest source rarity among the socketed IcyRetribution effects on the player's equipped magic
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/QueenEverflow.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/QueenEverflow.cs
index ba450bb51..20eccca7e 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/QueenEverflow.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/QueenEverflow.cs
@@ -23,6 +23,13 @@ public static class QueenEverflow {
{ BuffDurationKey, DefaultBuffDuration },
};
+ // Tooltip: "Queen's Everflow: +{0}% Regen per Stack (max {1})". The shard value IS the per-stack
+ // percentage, so {0} is the raw value; {1} comes from config and stays put as the rarity climbs.
+ public static void RegisterDisplayValues() {
+ MagicItem.RegisterDisplayValues(MagicEffectType.Everflow,
+ value => new object[] { value, (float)GetMaxStacks() });
+ }
+
private const string BuffName = "EL_QueenEverflow";
private static readonly int BuffHash = BuffName.GetStableHashCode();
private static SE_QueenEverflow _buffPrototype;
@@ -36,10 +43,9 @@ public static void OnDamageDealt(Character __instance, HitData hit, Character at
return;
}
- // Only fire on a kill. Predicted lethality: against a remote-owned victim the RPC
- // carrying this hit has not executed yet, so GetHealth() still reads pre-hit health and
- // a plain <= 0 check never fired in multiplayer.
- if (__instance.GetHealth() - hit.GetTotalDamage() > 0f) {
+ // Only fire on a kill. Against a remote-owned victim the RPC carrying this hit has not executed
+ // yet, so IsLethalHit estimates the post-hit health; against one this client owns, it has.
+ if (!MagicEffectsHelper.IsLethalHit(__instance, hit)) {
return;
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/StaminaOnKill.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/StaminaOnKill.cs
index a86ac0700..ab1371dbf 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/StaminaOnKill.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/StaminaOnKill.cs
@@ -1,15 +1,16 @@
-namespace EpicLoot.MagicItemEffects.Shards {
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
+
+namespace EpicLoot.MagicItemEffects.Shards {
// Restores a percentage of the player's maximum stamina when they kill an enemy
public static class StaminaOnKill {
// Postfix handler invoked by CharacterDamageDispatch (on-hit reaction).
public static void OnDamageDealt(Character __instance, HitData hit, Character attacker) {
var player = Player.m_localPlayer;
- // Kill check by predicted lethality: against a remote-owned victim the RPC carrying this
- // hit has not executed yet, so GetHealth() still reads pre-hit health and a plain <= 0
- // check never fired in multiplayer.
+ // Kill check via IsLethalHit: against a remote-owned victim the RPC carrying this hit has not
+ // executed yet, so the post-hit health is estimated; against one this client owns, it has.
if (hit == null || player == null || __instance == player || attacker != player
|| __instance.IsPlayer() || __instance.IsTamed()
- || __instance.GetHealth() - hit.GetTotalDamage() > 0f) {
+ || !MagicEffectsHelper.IsLethalHit(__instance, hit)) {
return;
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/Trailblazer.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/Trailblazer.cs
index 90ab4a3ec..ca0590a7f 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/Trailblazer.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/Trailblazer.cs
@@ -243,7 +243,7 @@ private void Update() {
hit.m_damage.m_fire = _tickDamage;
hit.m_hitType = HitData.HitType.Burning;
hit.SetAttacker(_owner);
- character.Damage(hit);
+ HitSource.DealBonusDamage(character, hit);
}
}
}
diff --git a/EpicLoot/src/Magic/MagicItemEffects/Shards/Wager.cs b/EpicLoot/src/Magic/MagicItemEffects/Shards/Wager.cs
index 39f655288..f7cb2b3c1 100644
--- a/EpicLoot/src/Magic/MagicItemEffects/Shards/Wager.cs
+++ b/EpicLoot/src/Magic/MagicItemEffects/Shards/Wager.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using EpicLoot.General;
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
using UnityEngine;
namespace EpicLoot.MagicItemEffects.Shards {
@@ -83,10 +84,9 @@ public static void OnDamageDealt(Character __instance, HitData hit) {
return;
}
- // Predicted lethality (see StaminaOnKill): against a remote-owned victim the post-hit
- // health is not visible yet, and the old plain check silently ATE the stake on every
- // real kill in multiplayer.
- if (__instance.GetHealth() - hit.GetTotalDamage() > 0f) {
+ // Against a remote-owned victim the post-hit health is not visible yet, so IsLethalHit estimates it;
+ // against one this client owns, the hit has already landed.
+ if (!MagicEffectsHelper.IsLethalHit(__instance, hit)) {
return; // the bet was lost -- the stake is gone
}
diff --git a/EpicLoot/src/Magic/MagicItemNames.cs b/EpicLoot/src/Magic/MagicItemNames.cs
index 99821f265..36aa37453 100755
--- a/EpicLoot/src/Magic/MagicItemNames.cs
+++ b/EpicLoot/src/Magic/MagicItemNames.cs
@@ -107,8 +107,7 @@ public static object GetPrefix(MagicItem magicItem)
}
var firstEffect = magicItem.Effects[0];
- var effectDef = MagicItemEffectDefinitions.Get(firstEffect.EffectType);
- if (effectDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(firstEffect.EffectType, out var effectDef))
{
return null;
}
@@ -129,8 +128,7 @@ public static object GetSuffix(MagicItem magicItem)
}
var secondEffect = magicItem.Effects[1];
- var effectDef = MagicItemEffectDefinitions.Get(secondEffect.EffectType);
- if (effectDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(secondEffect.EffectType, out var effectDef))
{
return null;
}
diff --git a/EpicLoot/src/Magic/PlayerExtensions.cs b/EpicLoot/src/Magic/PlayerExtensions.cs
index 89e949c04..293e2a6b0 100644
--- a/EpicLoot/src/Magic/PlayerExtensions.cs
+++ b/EpicLoot/src/Magic/PlayerExtensions.cs
@@ -1,4 +1,5 @@
using EpicLoot.LegendarySystem;
+using EpicLoot.src.Magic.MagicItemEffects.Helpers;
using System.Collections.Generic;
using System.Linq;
@@ -95,6 +96,11 @@ public static float GetTotalActiveMagicEffectValue(this Player player, string ef
{
totalValue -= magicItem.GetTotalEffectValue(effectType, scale, includeSocketed: true);
}
+ else if (ignoreThisItem == null)
+ {
+ // Only non-zero while a thrown weapon's projectile is landing (see HitSource.FiringWeapon).
+ totalValue += MagicEffectsHelper.GetFiringWeaponAdjustment(player, effectType, scale);
+ }
return totalValue;
}
diff --git a/EpicLoot/src/ShardStones/ShardSocketManager.cs b/EpicLoot/src/ShardStones/ShardSocketManager.cs
index ddb0195df..e93f5578b 100644
--- a/EpicLoot/src/ShardStones/ShardSocketManager.cs
+++ b/EpicLoot/src/ShardStones/ShardSocketManager.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using EpicLoot.Config;
using EpicLoot.Crafting;
@@ -87,8 +87,7 @@ public static bool CanSocket(ItemDrop.ItemData equipment, ItemDrop.ItemData inpu
return true;
}
- var def = MagicItemEffectDefinitions.Get(effect.EffectType);
- if (def == null) {
+ if (!MagicItemEffectDefinitions.TryGet(effect.EffectType, out var def)) {
reason = "$mod_epicloot_socket_invalidinput";
return false;
}
@@ -117,7 +116,7 @@ public static bool CanSocket(ItemDrop.ItemData equipment, ItemDrop.ItemData inpu
occupants.Add(new SocketOccupant(socket.ShardType, socket.Effect));
}
}
- if (!CheckDuplicateEffect(color, effect, rarity, occupants, out reason)) {
+ if (!CheckDuplicateEffect(color, effect, occupants, out reason)) {
return false;
}
@@ -159,8 +158,7 @@ public static bool CanCoexist(ItemDrop.ItemData equipment, ItemDrop.ItemData inp
return true;
}
- var def = MagicItemEffectDefinitions.Get(effect.EffectType);
- if (def == null) {
+ if (!MagicItemEffectDefinitions.TryGet(effect.EffectType, out var def)) {
reason = "$mod_epicloot_socket_invalidinput";
return false;
}
@@ -184,7 +182,7 @@ public static bool CanCoexist(ItemDrop.ItemData equipment, ItemDrop.ItemData inp
occupants.Add(new SocketOccupant(otherColor, otherEffect));
}
}
- if (!CheckDuplicateEffect(color, effect, rarity, occupants, out reason)) {
+ if (!CheckDuplicateEffect(color, effect, occupants, out reason)) {
return false;
}
@@ -208,7 +206,7 @@ public SocketOccupant(ShardType color, MagicItemEffect effect) {
// rune vs rune, rune vs shard, two colors landing on one effect -- stays under
// AllowDuplicateSocketedEffects, so nothing about those changes when stacking is off.
private static bool CheckDuplicateEffect(ShardType inputColor, MagicItemEffect effect,
- ItemRarity inputRarity, List occupants, out string reason) {
+ List occupants, out string reason) {
reason = null;
foreach (var occupant in occupants) {
@@ -233,7 +231,7 @@ private static bool CheckDuplicateEffect(ShardType inputColor, MagicItemEffect e
// An effect with no rarity-scaled value (Warmth, say) is a yes/no grant: a second one
// adds nothing whether it is decayed or not, so stacking it would only cost the player
// a socket. Same notion of "valueless" the BreakValueless removal mode uses.
- if (MagicItemEffectDefinitions.IsValuelessEffect(effect.EffectType, inputRarity)) {
+ if (MagicItemEffectDefinitions.IsValuelessEffect(effect.EffectType)) {
reason = "$mod_epicloot_socket_nostackbinary";
return false;
}
@@ -356,9 +354,8 @@ private static List> GroupSocketsByColor(MagicItem magicItem, float[]
// How the given socketed entry is allowed to leave its socket. Derived live from config and the
// socket's own data -- nothing is persisted, so a config change applies to every existing item
- // immediately. `sourceRarity` is the shard/runestone's own rarity, the same key the shard grid
- // is indexed by in ResolveSocketedEffect.
- public static SocketRemoval GetRemovalPolicy(ShardType color, MagicItemEffect effect, ItemRarity sourceRarity) {
+ // immediately.
+ public static SocketRemoval GetRemovalPolicy(ShardType color, MagicItemEffect effect) {
if (color == ShardType.None) {
switch (ELConfig.RuneSocketRemovalMode.Value) {
case RuneSocketMode.Break:
@@ -372,7 +369,7 @@ public static SocketRemoval GetRemovalPolicy(ShardType color, MagicItemEffect ef
switch (ELConfig.ShardSocketRemovalMode.Value) {
case ShardSocketMode.BreakValueless:
- return IsValuelessGrant(effect, sourceRarity) ? SocketRemoval.BreakOnly : SocketRemoval.Free;
+ return IsValuelessGrant(effect) ? SocketRemoval.BreakOnly : SocketRemoval.Free;
case ShardSocketMode.BreakAll:
return SocketRemoval.BreakOnly;
case ShardSocketMode.Permanent:
@@ -386,14 +383,14 @@ public static SocketRemoval GetRemovalPolicy(ShardType color, MagicItemEffect ef
public static SocketRemoval GetRemovalPolicy(SocketedEffect socket) {
return socket == null
? SocketRemoval.Free
- : GetRemovalPolicy(socket.ShardType, socket.Effect, socket.SourceRarity);
+ : GetRemovalPolicy(socket.ShardType, socket.Effect);
}
// Policy for a socketable item sitting in the socket grid (UI). Anything that isn't a valid
// socketable has no policy to enforce.
public static SocketRemoval GetRemovalPolicy(ItemDrop.ItemData equipment, ItemDrop.ItemData socketed) {
return ResolveSocketedEffect(equipment, socketed, out var effect, out var color, out var rarity)
- ? GetRemovalPolicy(color, effect, rarity)
+ ? GetRemovalPolicy(color, effect)
: SocketRemoval.Free;
}
@@ -402,9 +399,8 @@ public static SocketRemoval GetRemovalPolicy(ItemDrop.ItemData equipment, ItemDr
// per-rarity number (that number is meaningless for a binary effect). A shard that grants
// nothing at all in a slot is deliberately NOT valueless in this sense: the player gained
// nothing from it, so it owes no commitment and stays freely removable.
- private static bool IsValuelessGrant(MagicItemEffect effect, ItemRarity sourceRarity) {
- return effect != null &&
- MagicItemEffectDefinitions.IsValuelessEffect(effect.EffectType, sourceRarity);
+ private static bool IsValuelessGrant(MagicItemEffect effect) {
+ return effect != null && MagicItemEffectDefinitions.IsValuelessEffect(effect.EffectType);
}
// The player-facing reason a socketed item may not simply be dragged out.
diff --git a/EpicLoot/src/Tempering/EnchantmentElement.cs b/EpicLoot/src/Tempering/EnchantmentElement.cs
index 1ecfe67fc..5d913b431 100644
--- a/EpicLoot/src/Tempering/EnchantmentElement.cs
+++ b/EpicLoot/src/Tempering/EnchantmentElement.cs
@@ -39,15 +39,18 @@ public void Awake()
elements.Add(this);
}
- public void SetEffect(MagicItemEffect effect, ItemRarity rarity)
+ // Takes the whole item rather than its rarity: a unique's range comes from its legendary entry when
+ // that declares one, and the row must show the same range the temper will actually step against.
+ public void SetEffect(MagicItemEffect effect, MagicItem magicItem)
{
+ ItemRarity rarity = magicItem.Rarity;
_effect = effect;
_definition = MagicItemEffectDefinitions.Get(effect.EffectType);
- _values = _definition.GetValuesForRarity(rarity);
+ _values = TemperData.GetTemperRange(magicItem, effect);
string text = MagicItem.GetEffectTextGeneric(_definition, $"{effect.EffectValue}");
- canBeTempered = _values != null;
+ canBeTempered = TemperData.IsTemperable(magicItem, effect);
label.text = text;
- if (_values == null)
+ if (!canBeTempered)
{
button.interactable = false;
label.color = Color.gray;
diff --git a/EpicLoot/src/Tempering/TemperData.cs b/EpicLoot/src/Tempering/TemperData.cs
index 3c1fa7de8..dcdca0c55 100644
--- a/EpicLoot/src/Tempering/TemperData.cs
+++ b/EpicLoot/src/Tempering/TemperData.cs
@@ -2,7 +2,6 @@
using System.Linq;
using System.Text;
using EpicLoot.Config;
-using EpicLoot.LegendarySystem;
using UnityEngine;
namespace EpicLoot;
@@ -40,28 +39,30 @@ public class TemperData
private readonly int indexOfEffect;
// The value range a temper of this effect works against: the legendary/mythic guaranteed-effect
- // range when the item is a unique, the rarity table otherwise -- the same resolution the detailed
- // tooltip uses (MagicItem.GetEffectDetailBlock). Null means the effect has no rolled value at this
- // rarity (a valueless effect like Warmth) and cannot be tempered.
+ // range when the unique declares one, the rarity table otherwise -- the same resolution the detailed
+ // tooltip uses (MagicItemEffectDefinitions.GetRollRange). Stock legendaries.json declares no such
+ // range, so falling back to the rarity table is what keeps a unique's effects temperable at all.
+ // Null means the effect has no rolled value at this rarity (a valueless effect like Warmth) and
+ // cannot be tempered.
private static MagicItemEffectDefinition.ValueDef ResolveValues(MagicItem magicItem,
MagicItemEffectDefinition def)
{
- return string.IsNullOrEmpty(magicItem.LegendaryID)
- ? def.GetValuesForRarity(magicItem.Rarity)
- : UniqueLegendaryHelper.GetLegendaryEffectValues(magicItem.LegendaryID, def.Type);
+ return MagicItemEffectDefinitions.GetRollRange(def, magicItem.Rarity, magicItem.LegendaryID);
+ }
+
+ // The range the temper panel shows and steps against for this effect on this item. Null for an
+ // effect with no registered definition as well: a stand-in's made-up range is nothing to temper by.
+ public static MagicItemEffectDefinition.ValueDef GetTemperRange(MagicItem magicItem, MagicItemEffect effect)
+ {
+ return MagicItemEffectDefinitions.GetRollRange(magicItem, effect.EffectType);
}
// Whether the effect can be tempered at all: it needs a value range and a positive increment to
// step by. The enchantment list uses this to leave untemperable effects out entirely.
public static bool IsTemperable(MagicItem magicItem, MagicItemEffect effect)
{
- MagicItemEffectDefinition def = MagicItemEffectDefinitions.Get(effect.EffectType);
- if (def == null)
- {
- return false;
- }
-
- MagicItemEffectDefinition.ValueDef values = ResolveValues(magicItem, def);
+ // An effect with no registered definition has no real range to temper against.
+ MagicItemEffectDefinition.ValueDef values = GetTemperRange(magicItem, effect);
return values != null && values.Increment > 0f && values.MaxValue > 0f;
}
@@ -71,8 +72,9 @@ public TemperData(ItemDrop.ItemData itemData, string effectType)
magicItem = itemData.GetMagicItem();
selectedEffect = magicItem.GetEffects(effectType)[0];
+ // Get() for display (never null); the range itself comes only from a registered definition.
selectedDefinition = MagicItemEffectDefinitions.Get(effectType);
- selectedValues = ResolveValues(magicItem, selectedDefinition);
+ selectedValues = GetTemperRange(magicItem, selectedEffect);
rarityColor = EpicLoot.GetRarityColor(magicItem.Rarity);
// The panel filters untemperable effects out via IsTemperable, so this only trips if a config
@@ -187,8 +189,10 @@ public void OnFail()
if (effect.EffectType == effectToReduce)
{
- MagicItemEffectDefinition def = MagicItemEffectDefinitions.Get(effect.EffectType);
- MagicItemEffectDefinition.ValueDef values = def != null ? ResolveValues(magicItem, def) : null;
+ MagicItemEffectDefinition.ValueDef values =
+ MagicItemEffectDefinitions.TryGet(effect.EffectType, out MagicItemEffectDefinition def)
+ ? ResolveValues(magicItem, def)
+ : null;
// SelectWeightedEffect only offers effects with a value range, but guard anyway -- a
// failed lookup must degrade to "nothing reduced", never throw mid-temper.
if (values != null)
@@ -197,9 +201,10 @@ public void OnFail()
_tempValue = newValue.EffectValue;
_tempIncrement = values.Increment;
// A failure can never push an effect below the worst legitimate roll for the
- // rarity. An effect already at (or somehow below) MinValue just stays put.
- newValue.EffectValue = Mathf.Max(values.MinValue,
- newValue.EffectValue - values.Increment);
+ // rarity. An effect already at (or somehow below) MinValue just stays put: the
+ // outer Min stops the floor from raising it.
+ newValue.EffectValue = Mathf.Min(newValue.EffectValue,
+ Mathf.Max(values.MinValue, newValue.EffectValue - values.Increment));
_tempUpdatedValue = newValue.EffectValue;
_tempEffect = newValue;
}
@@ -227,8 +232,7 @@ private string SelectWeightedEffect(List effects)
for (int i = 0; i < effects.Count; ++i)
{
MagicItemEffect effect = effects[i];
- MagicItemEffectDefinition def = MagicItemEffectDefinitions.Get(effect.EffectType);
- MagicItemEffectDefinition.ValueDef values = def != null ? ResolveValues(magicItem, def) : null;
+ MagicItemEffectDefinition.ValueDef values = GetTemperRange(magicItem, effect);
if (values == null || values.MaxValue <= 0f) continue;
float num = effect.EffectValue / values.MaxValue;
candidates.Add(effect);
diff --git a/EpicLoot/src/Tempering/TemperPanel.cs b/EpicLoot/src/Tempering/TemperPanel.cs
index 2b9499d93..588a468d2 100644
--- a/EpicLoot/src/Tempering/TemperPanel.cs
+++ b/EpicLoot/src/Tempering/TemperPanel.cs
@@ -426,10 +426,11 @@ private bool HaveRequirements(Player player) {
}
private bool ConsumeRequirements(Player player) {
- if (!selectedItemElement) {
- return false;
- }
- if (!TemperMan.IsTemperableRarity(selectedItemElement._magicItem.Rarity)) {
+ // Re-checked here, at completion, not just when the button was enabled: vanilla
+ // ConsumeResources quietly takes only what is present, so materials spent (or sold to the
+ // trader) during the countdown used to buy a temper at a discount. HaveRequirements also
+ // carries the rarity check and the no-cost cheat.
+ if (!HaveRequirements(player)) {
return false;
}
if (player.NoCostCheat()) {
@@ -472,6 +473,10 @@ public void FillItemList(Player player) {
element.SetItem(item);
element.index = i;
element.button.onClick.AddListener(() => {
+ // A temper in progress acts on the selection it started with.
+ if (IsTempering()) {
+ return;
+ }
SetSelectedItem(element);
});
}
@@ -555,12 +560,16 @@ public void FillEnchantmentList(string selectedEffect = null) {
instance.SetActive(true);
if (instance.TryGetComponent(out EnchantmentElement element)) {
element.selected.color = EpicLoot.GetRarityColorARGB(selectedItemElement._magicItem.Rarity);
- element.SetEffect(effect, selectedItemElement._magicItem.Rarity);
+ element.SetEffect(effect, selectedItemElement._magicItem);
// List position, NOT the effect index: gamepad navigation walks
// EnchantmentElement.elements by this value, and skipping untemperable effects
// above desyncs the two (the up-branch then indexed past the end of the list).
element.index = listIndex++;
element.button.onClick.AddListener(() => {
+ // A temper in progress acts on the selection it started with.
+ if (IsTempering()) {
+ return;
+ }
SetSelectedEnchantment(element);
});
if (!string.IsNullOrEmpty(selectedEffect) && effect.EffectType == selectedEffect) {
@@ -614,8 +623,17 @@ public void FillRequirementList() {
public void UpdateTemperButton() {
temperButton.interactable = CanTemper();
}
+ // True while the countdown runs. Selection input is ignored for that long -- the temper completes
+ // against whatever is selected when it ends, so re-selecting mid-way used to temper a different
+ // item or effect than the one started (and paid for). The guard sits on the input paths, not in
+ // SetSelectedItem/SetSelectedEnchantment: OnTemper calls those itself while the panel is up.
+ public bool IsTempering() => progressPanel != null && progressPanel.activeSelf;
+
public void OnStartTemper() {
- if (!Player.m_localPlayer || temperData == null) {
+ // The button's interactable state is only refreshed on selection changes, so it can be
+ // stale; check again rather than trusting it.
+ if (IsTempering() || !CanTemper()) {
+ UpdateTemperButton();
return;
}
progressBar.SetValue(0f);
@@ -645,6 +663,10 @@ public void OnCancel() {
progressPanel.SetActive(false);
progressTimer = -1f;
temperButton.gameObject.SetActive(true);
+ StopStartEffects();
+ }
+
+ private void StopStartEffects() {
if (startEffects != null) {
for (int i = 0; i < startEffects.Length; ++i) {
GameObject effect = startEffects[i];
@@ -660,8 +682,18 @@ public void OnTemper() {
if (!Player.m_localPlayer || temperData == null) {
return;
}
- if (!ConsumeRequirements(Player.m_localPlayer)) {
- EpicLoot.LogWarning("Tried to temper, but failed to consume requirements");
+ // The item may have left the inventory during the countdown (sold, dropped); a temper would
+ // only charge for an item the player no longer has.
+ if (!selectedItemElement || !Player.m_localPlayer.GetInventory().ContainsItem(selectedItemElement._item) ||
+ !ConsumeRequirements(Player.m_localPlayer)) {
+ EpicLoot.LogWarning("Tried to temper, but the item or the requirements were no longer there");
+ // Treated like a cancel: nothing was taken, the item is untouched and the start effects
+ // stop. UpdateProgress puts the temper button back once this returns.
+ StopStartEffects();
+ Player.m_localPlayer.Message(MessageHud.MessageType.Center, "$msg_missingrequirement");
+ FillRequirementList();
+ UpdateCurrencies();
+ UpdateTemperButton();
return;
}
// The outcome is decided here, at execution -- not at selection -- so re-selecting an
@@ -680,6 +712,10 @@ public void OnTemper() {
Player.m_localPlayer.GetInventory().RemoveOneItem(itemToRemove);
ItemElement.elements.Remove(selectedItemElement);
Destroy(selectedItemElement.gameObject);
+ // Gamepad up/down steps by index; without closing the gap it skipped an entry.
+ for (int i = 0; i < ItemElement.elements.Count; ++i) {
+ ItemElement.elements[i].index = i;
+ }
SetSelectedItem(null);
UpdateLog(Localization.instance.Localize($"{itemToRemove.GetDisplayName()} destroyed"));
temperDoneEffects.Create(Player.m_localPlayer.transform.position, Quaternion.identity);
@@ -728,6 +764,17 @@ public void UpdateGamepadInput() {
bool didTakeGamepadInput = takeGamepadInput;
gamepadPanelHints.SetActive(true);
+
+ // Everything below except the tooltip scrolling moves or clears the selection, and a temper
+ // in progress completes against the selection it started with. The progress panel's
+ // cancel button still answers.
+ if (IsTempering()) {
+ if (takeGamepadInput) {
+ UpdateGamepadTooltipScroll();
+ }
+ return;
+ }
+
if (!takeGamepadInput && ZInput.GetButtonDown("JoyTabRight")) {
selectedFrame.enabled = true;
takeGamepadInput = true;
@@ -843,21 +890,7 @@ public void UpdateGamepadInput() {
}
}
- if (ZInput.GetButton("JoyRStickUp")) {
- if (ZInput.GetButton("JoyRTrigger")) {
- infoTooltip._scrollbar.value = Mathf.Clamp01(infoTooltip._scrollbar.value + 0.1f);
- } else {
- logTooltip._scrollbar.value = Mathf.Clamp01(logTooltip._scrollbar.value + 0.1f);
- }
- }
-
- if (ZInput.GetButton("JoyRStickDown")) {
- if (ZInput.GetButton("JoyRTrigger")) {
- infoTooltip._scrollbar.value = Mathf.Clamp01(infoTooltip._scrollbar.value - 0.1f);
- } else {
- logTooltip._scrollbar.value = Mathf.Clamp01(logTooltip._scrollbar.value - 0.1f);
- }
- }
+ UpdateGamepadTooltipScroll();
} else {
if (!isFirstChild || selectedItemElement) {
transform.SetAsFirstSibling();
@@ -868,6 +901,24 @@ public void UpdateGamepadInput() {
}
}
+ private void UpdateGamepadTooltipScroll() {
+ if (ZInput.GetButton("JoyRStickUp")) {
+ if (ZInput.GetButton("JoyRTrigger")) {
+ infoTooltip._scrollbar.value = Mathf.Clamp01(infoTooltip._scrollbar.value + 0.1f);
+ } else {
+ logTooltip._scrollbar.value = Mathf.Clamp01(logTooltip._scrollbar.value + 0.1f);
+ }
+ }
+
+ if (ZInput.GetButton("JoyRStickDown")) {
+ if (ZInput.GetButton("JoyRTrigger")) {
+ infoTooltip._scrollbar.value = Mathf.Clamp01(infoTooltip._scrollbar.value - 0.1f);
+ } else {
+ logTooltip._scrollbar.value = Mathf.Clamp01(logTooltip._scrollbar.value - 0.1f);
+ }
+ }
+ }
+
[HarmonyPatch(typeof(StoreGui), nameof(StoreGui.UpdateRecipeGamepadInput))]
private static class StoreGui_UpdateRecipeGamepadInput_Patch {
private static bool Prefix() {
diff --git a/EpicLoot/src/Terminal/Commands/MagicItem.cs b/EpicLoot/src/Terminal/Commands/MagicItem.cs
index de18e7692..5afba77d2 100644
--- a/EpicLoot/src/Terminal/Commands/MagicItem.cs
+++ b/EpicLoot/src/Terminal/Commands/MagicItem.cs
@@ -76,8 +76,7 @@ private static List GetSpawnMagicItemOptions(string[] args)
private static List GetValidMagicItemNamesWithRequirements(string effectType)
{
List result = [];
- var definition = MagicItemEffectDefinitions.Get(effectType);
- if (definition == null)
+ if (!MagicItemEffectDefinitions.TryGet(effectType, out var definition))
{
return result;
}
@@ -116,8 +115,7 @@ private static void SpawnMagicItemWithEffect(Terminal.ConsoleEventArgs args)
string itemPrefabNameArg = args.GetString(2);
args.Context.PrintInfo($"magicitem - {itemPrefabNameArg} with effect: {effectArg}");
- MagicItemEffectDefinition magicItemEffectDef = MagicItemEffectDefinitions.Get(effectArg);
- if (magicItemEffectDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(effectArg, out MagicItemEffectDefinition magicItemEffectDef))
{
args.Context.PrintWarning($"> Could not find effect: {effectArg}");
return;
diff --git a/EpicLoot/src/Tooltip/MagicTooltipShard.cs b/EpicLoot/src/Tooltip/MagicTooltipShard.cs
index cd9d497ac..e5f8b1759 100644
--- a/EpicLoot/src/Tooltip/MagicTooltipShard.cs
+++ b/EpicLoot/src/Tooltip/MagicTooltipShard.cs
@@ -1,4 +1,4 @@
-using EpicLoot.ShardStones;
+using EpicLoot.ShardStones;
namespace EpicLoot;
@@ -23,7 +23,7 @@ private void AddShardPreview()
// socket for good, so the commitment has to be stated here too.
if (color != ShardType.None)
{
- AppendBlanketRemovalWarning(ShardSocketManager.GetRemovalPolicy(color, null, rarity));
+ AppendBlanketRemovalWarning(ShardSocketManager.GetRemovalPolicy(color, null));
}
return;
}
@@ -34,15 +34,14 @@ private void AddShardPreview()
// ends up granting -- i.e. the BreakAll/Permanent modes. When one of those is on, say so once
// up front instead of repeating a marker on every line, and note that it covers the slots not
// even listed below (those that grant nothing).
- var blanket = ShardSocketManager.GetRemovalPolicy(color, null, rarity);
+ var blanket = ShardSocketManager.GetRemovalPolicy(color, null);
// A uniform shard (e.g. a boss shard) grants one effect on every slot it is allowed into.
if (def.UniformEffect != null)
{
if (def.UniformEffect.ValuesPerRarity.TryGetValue(rarity, out var uniformValue))
{
- var uniformDef = MagicItemEffectDefinitions.Get(def.UniformEffect.EffectType);
- if (uniformDef != null)
+ if (MagicItemEffectDefinitions.TryGet(def.UniformEffect.EffectType, out var uniformDef))
{
var allSlots = Localization.instance.Localize("$mod_epicloot_shard_allslots");
var uniformText = MagicItem.GetEffectText(uniformDef, uniformValue);
@@ -69,8 +68,7 @@ private void AddShardPreview()
continue;
}
- var effectMagicDef = MagicItemEffectDefinitions.Get(effectDef.EffectType);
- if (effectMagicDef == null)
+ if (!MagicItemEffectDefinitions.TryGet(effectDef.EffectType, out var effectMagicDef))
{
continue;
}
@@ -95,7 +93,7 @@ private static string PreviewRemovalTag(SocketRemoval blanket, ShardType color,
return "";
}
- var policy = ShardSocketManager.GetRemovalPolicy(color, new MagicItemEffect(effectType, value), rarity);
+ var policy = ShardSocketManager.GetRemovalPolicy(color, new MagicItemEffect(effectType, value));
switch (policy)
{
case SocketRemoval.BreakOnly:
diff --git a/EpicLoot/src/Tooltip/MagicTooltip_Helpers.cs b/EpicLoot/src/Tooltip/MagicTooltip_Helpers.cs
index 9f6ce7201..26eb58d40 100644
--- a/EpicLoot/src/Tooltip/MagicTooltip_Helpers.cs
+++ b/EpicLoot/src/Tooltip/MagicTooltip_Helpers.cs
@@ -101,12 +101,24 @@ public static float GetBlockPowerValue(ItemDrop.ItemData item, MagicItem magicIt
}
///
- /// Adds the magic effect bonus and the m_timedBlockBonus (a multiplier to base block power)
+ /// How much stronger a parry is than a normal block with this item. Vanilla multiplies the parry's block
+ /// power by m_timedBlockBonus, and ModifyParry raises the base block power a parry starts from
+ /// (ModifyBlock.GetBlockPowerValue), so the two multiply -- adding them understated the bonus.
///
public static float GetParryBonusValue(ItemDrop.ItemData item, MagicItem magicItem, int quality, out bool hasModifiers)
{
hasModifiers = magicItem.HasEffect(MagicEffectType.ModifyParry) ||
magicItem.HasEffect(MagicEffectType.ModifyParryLowHealth);
- return item.m_shared.m_timedBlockBonus + ModifyBlock.GetMultiplier(null, MagicEffectType.ModifyParry, item);
+
+ // The same base block power vanilla's GetBaseBlockPower returns, before EpicLoot's postfixes.
+ float baseBlock = item.m_shared.m_blockPower + Mathf.Max(0, quality - 1) * item.m_shared.m_blockPowerPerLevel;
+ Player player = ModifyBlock.RunModifyBlockPatchs(item) ? Player.m_localPlayer : null;
+ float normalBlock = ModifyBlock.GetBlockPowerValue(player, item, baseBlock, false);
+ if (normalBlock <= 0f)
+ {
+ return item.m_shared.m_timedBlockBonus;
+ }
+
+ return item.m_shared.m_timedBlockBonus * ModifyBlock.GetBlockPowerValue(player, item, baseBlock, true) / normalBlock;
}
}
\ No newline at end of file
diff --git a/EpicLoot/src/UI/Enchanting/EnchantingTableUI.cs b/EpicLoot/src/UI/Enchanting/EnchantingTableUI.cs
index 120249ead..153c96734 100644
--- a/EpicLoot/src/UI/Enchanting/EnchantingTableUI.cs
+++ b/EpicLoot/src/UI/Enchanting/EnchantingTableUI.cs
@@ -1,4 +1,6 @@
-using EpicLoot.CraftingV2;
+using System;
+using System.Collections.Generic;
+using EpicLoot.CraftingV2;
using UnityEngine;
using UnityEngine.UI;
@@ -24,6 +26,9 @@ public class EnchantingTableUI : MonoBehaviour
public static EnchantingTableUI instance { get; set; }
private int _hiddenFrames;
+ private GameObject[] _gamepadHintContainers = Array.Empty();
+ private bool _gamepadHintsShown = true;
+ private bool _correctingTab;
public void Awake()
{
@@ -37,6 +42,7 @@ public void Start()
EnchantingUIController.SetupUIAudioSource(Audio);
instance.SetupTabs();
+ instance.CollectGamepadHints();
EnchantingUIAugaFixup.AugaFixup(this);
}
@@ -83,7 +89,132 @@ private void SetupTabs()
}
}
+ // Overrides the prefab's bindings, which put the tab bar on the bumpers that
+ // MultiSelectListFocusController also claims, and left Tab bound here while Update uses it to
+ // close the window.
+ TabHandler.m_gamepadInput = true;
+ TabHandler.m_gamepadNavigateLeft = "JoyLTrigger";
+ TabHandler.m_gamepadNavigateRight = "JoyRTrigger";
+ TabHandler.m_tabKeyInput = false;
+
+ if (TabScrim != null && !TabHandler.m_blockingElements.Contains(TabScrim))
+ {
+ TabHandler.m_blockingElements.Add(TabScrim);
+ }
+
+ TabHandler.ActiveTabChanged += OnActiveTabChanged;
+
+ SortTabsIntoVisualOrder();
+ RefreshTabActivation();
+ }
+
+ // The prefab lists Upgrade before Rune while the tab bar shows Rune before Upgrade, and TabHandler
+ // cycles by list index, so the triggers would visit the last two tabs in the wrong order.
+ private void SortTabsIntoVisualOrder()
+ {
+ TabHandler.Tab selected = TabHandler.m_selected >= 0 && TabHandler.m_selected < TabHandler.m_tabs.Count
+ ? TabHandler.m_tabs[TabHandler.m_selected]
+ : null;
+
+ TabHandler.m_tabs.Sort((a, b) => GetTabSiblingIndex(a).CompareTo(GetTabSiblingIndex(b)));
+
+ if (selected != null)
+ {
+ TabHandler.m_selected = TabHandler.m_tabs.IndexOf(selected);
+ }
+ }
+
+ private static int GetTabSiblingIndex(TabHandler.Tab tab)
+ {
+ return tab.m_button != null ? tab.m_button.transform.GetSiblingIndex() : int.MaxValue;
+ }
+
+ // Also called directly, not just from the event: TabHandler picks its default tab in Start, which
+ // may run before the subscription above.
+ private void RefreshTabActivation()
+ {
EnchantingUIController.TabActivation(this);
+
+ if (TabHandler != null)
+ {
+ OnActiveTabChanged(TabHandler.GetActiveTab());
+ }
+ }
+
+ // TabHandler's own cycling skips tabs whose button is null, not ones config has deactivated, so it
+ // can land on a hidden tab and show its empty page.
+ private void OnActiveTabChanged(int index)
+ {
+ if (_correctingTab || TabHandler == null || index < 0 ||
+ index >= TabHandler.m_tabs.Count || IsTabAvailable(index))
+ {
+ return;
+ }
+
+ // Still readable here: this fires in the same frame as the press that moved the tab.
+ int direction = ZInput.GetButtonDown(TabHandler.m_gamepadNavigateLeft) ? -1 : 1;
+
+ int tabCount = TabHandler.m_tabs.Count;
+ for (int offset = 1; offset < tabCount; ++offset)
+ {
+ int candidate = ((index + offset * direction) % tabCount + tabCount) % tabCount;
+ if (!IsTabAvailable(candidate))
+ {
+ continue;
+ }
+
+ _correctingTab = true;
+ try
+ {
+ TabHandler.SetActiveTab(candidate);
+ }
+ finally
+ {
+ _correctingTab = false;
+ }
+
+ return;
+ }
+ }
+
+ private bool IsTabAvailable(int index)
+ {
+ TabHandler.Tab tab = TabHandler.m_tabs[index];
+ return tab.m_button != null && tab.m_button.gameObject.activeSelf;
+ }
+
+ // Matches the prefab's hint containers by name; they ship always-on with nothing to toggle them.
+ private void CollectGamepadHints()
+ {
+ List hints = new List();
+ foreach (Transform child in Root.GetComponentsInChildren(true))
+ {
+ if (child.name == "GamepadHints" || child.name == "TabGamepadHints")
+ {
+ hints.Add(child.gameObject);
+ }
+ }
+
+ _gamepadHintContainers = hints.ToArray();
+ RefreshGamepadHints();
+ }
+
+ private void RefreshGamepadHints()
+ {
+ bool show = ZInput.IsGamepadActive();
+ if (show == _gamepadHintsShown)
+ {
+ return;
+ }
+
+ _gamepadHintsShown = show;
+ foreach (GameObject hint in _gamepadHintContainers)
+ {
+ if (hint != null)
+ {
+ hint.SetActive(show);
+ }
+ }
}
public static void Show(EnchantingTable source)
@@ -171,6 +302,8 @@ public void Update()
_hiddenFrames = 0;
+ RefreshGamepadHints();
+
// The player died (or logged out) with the table open: close it -- nothing else does,
// and every dereference below would NRE each frame over the death screen.
if (Player.m_localPlayer == null)
@@ -216,12 +349,12 @@ public void Update()
public static void UpdateTabActivation()
{
- EnchantingUIController.TabActivation(instance);
+ instance?.RefreshTabActivation();
}
public static void UpdateUpgradeActivation()
{
- EnchantingUIController.TabActivation(instance);
+ UpdateTabActivation();
}
public void LockTabs()
diff --git a/EpicLoot/src/UI/Enchanting/EnchantingTableUIPanelBase.cs b/EpicLoot/src/UI/Enchanting/EnchantingTableUIPanelBase.cs
index ab7d51804..33d19b0f0 100644
--- a/EpicLoot/src/UI/Enchanting/EnchantingTableUIPanelBase.cs
+++ b/EpicLoot/src/UI/Enchanting/EnchantingTableUIPanelBase.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using EpicLoot.CraftingV2;
using TMPro;
using UnityEngine;
@@ -10,6 +11,8 @@ public abstract class EnchantingTableUIPanelBase : MonoBehaviour
{
public const float CountdownTime = 0.8f;
+ public const string MainActionButton = "JoyButtonX";
+
public MultiSelectItemList AvailableItems;
public Button MainButton;
public GameObject LevelDisplay;
@@ -27,6 +30,8 @@ public abstract class EnchantingTableUIPanelBase : MonoBehaviour
protected string _defaultButtonLabelText;
protected bool _locked;
+ private GameObject _mainButtonGamepadHint;
+
protected abstract void DoMainAction();
protected abstract void OnSelectedItemsChanged();
@@ -49,12 +54,81 @@ public virtual void Awake()
}
_defaultButtonLabelText = _useTMP ? _tmpButtonLabel.text : _buttonLabel.text;
+ _mainButtonGamepadHint = FindGamepadHint(MainButton.transform);
}
EnchantingUIController.SetupUIAudioSource(Audio);
EnchantingUIController.SetupUIAudioSources(gameObject);
}
+ // Matches the prefab's glyph child by name: "Hint" in most panels, "Hint-1" in the ones with two.
+ private static GameObject FindGamepadHint(Transform button)
+ {
+ for (int i = 0; i < button.childCount; ++i)
+ {
+ Transform child = button.GetChild(i);
+ if (child.name.StartsWith("Hint", StringComparison.Ordinal))
+ {
+ return child.gameObject;
+ }
+ }
+
+ return null;
+ }
+
+ private void UpdateMainButtonGamepadInput()
+ {
+ if (MainButton == null)
+ {
+ return;
+ }
+
+ bool usable = ZInput.IsGamepadActive() && (_inProgress || !_locked) && MainButton.IsInteractable();
+
+ if (_mainButtonGamepadHint != null && _mainButtonGamepadHint.activeSelf != usable)
+ {
+ _mainButtonGamepadHint.SetActive(usable);
+ }
+
+ if (usable && ZInput.GetButtonDown(MainActionButton))
+ {
+ ZInput.ResetButtonStatus(MainActionButton);
+ OnMainButtonClicked();
+ }
+ }
+
+ // The d-pad's horizontal axis still moves the player's hotbar selection behind this panel, so it
+ // is eaten here whether or not the panel answers it.
+ private void UpdateDPadHorizontalInput()
+ {
+ if (!ZInput.IsGamepadActive())
+ {
+ return;
+ }
+
+ int direction;
+ if (ZInput.GetButtonDown("JoyDPadLeft"))
+ {
+ direction = -1;
+ ZInput.ResetButtonStatus("JoyDPadLeft");
+ }
+ else if (ZInput.GetButtonDown("JoyDPadRight"))
+ {
+ direction = 1;
+ ZInput.ResetButtonStatus("JoyDPadRight");
+ }
+ else
+ {
+ return;
+ }
+
+ OnDPadHorizontal(direction);
+ }
+
+ protected virtual void OnDPadHorizontal(int direction)
+ {
+ }
+
protected virtual void OnMainButtonClicked()
{
if (MainActionSFX != null)
@@ -78,6 +152,9 @@ public virtual void DeselectAll()
public virtual void Update()
{
+ UpdateMainButtonGamepadInput();
+ UpdateDPadHorizontalInput();
+
if (ProgressBar != null)
{
ProgressBar.gameObject.SetActive(_inProgress);
@@ -207,7 +284,9 @@ public virtual void Unlock()
EnchantingTableUI.instance.UnlockTabs();
}
- protected static bool LocalPlayerCanAffordCost(List cost)
+ // Internal as well as protected: EnchantingUIController re-checks the identify cost with it after
+ // rolling, right before charging.
+ protected internal static bool LocalPlayerCanAffordCost(List cost)
{
if (Player.m_localPlayer.NoCostCheat())
{
diff --git a/EpicLoot/src/UI/Enchanting/InventoryManagement.cs b/EpicLoot/src/UI/Enchanting/InventoryManagement.cs
index b9aa29059..0cb2c3dcc 100644
--- a/EpicLoot/src/UI/Enchanting/InventoryManagement.cs
+++ b/EpicLoot/src/UI/Enchanting/InventoryManagement.cs
@@ -172,8 +172,16 @@ private void DropItem(ItemDrop.ItemData item)
// Both removal paths spend the player's own inventory first and only charge the shortfall to
// external providers, matching how the read paths above report availability.
- public void RemoveExactItem(ItemDrop.ItemData item, int amount)
+ // Returns how many were actually removed, which is less than amount when the item (or part of its
+ // stack) was no longer anywhere the table can reach. Callers that hand something out in exchange
+ // must pay out for that number, not for what they asked for.
+ public int RemoveExactItem(ItemDrop.ItemData item, int amount)
{
+ if (item == null || amount <= 0)
+ {
+ return 0;
+ }
+
Inventory inventory = GetInventory();
int taken = 0;
@@ -187,8 +195,10 @@ public void RemoveExactItem(ItemDrop.ItemData item, int amount)
int shortfall = amount - taken;
if (shortfall > 0)
{
- API.RemoveExactProviderItem(item, shortfall);
+ taken += API.RemoveExactProviderItem(item, shortfall);
}
+
+ return taken;
}
public void RemoveItem(ItemDrop.ItemData item)
diff --git a/EpicLoot/src/UI/Enchanting/MultiSelectItemList.cs b/EpicLoot/src/UI/Enchanting/MultiSelectItemList.cs
index 404903f7c..accfe9b16 100644
--- a/EpicLoot/src/UI/Enchanting/MultiSelectItemList.cs
+++ b/EpicLoot/src/UI/Enchanting/MultiSelectItemList.cs
@@ -31,7 +31,7 @@ public class InventoryItemListElement : IListElement
public string GetDisplayNameSuffix() => string.Empty;
}
- public class MultiSelectItemList : MonoBehaviour
+ public class MultiSelectItemList : MonoBehaviour, IGamepadFocusPane
{
public enum SortMode { Rarity, Name, Quantity }
@@ -90,84 +90,7 @@ public void Update()
return;
}
- int elementCount = ListContainer.childCount;
- MultiSelectItemListElement focusedElement = GetFocusedElement();
- if (focusedElement == null)
- {
- return;
- }
-
- int focusedElementIndex = focusedElement.transform.GetSiblingIndex();
- GridLayoutGroup grid = ListContainer.GetComponent();
- if (ListContainer.GetComponent() != null)
- {
- if (focusedElementIndex > 0 && ZInput.GetButtonDown("JoyLStickUp"))
- {
- focusedElement.GiveFocus(false);
- MultiSelectItemListElement newElement = GetElement(focusedElementIndex - 1);
- newElement.GiveFocus(true);
- CenterOnItem(newElement);
- ZInput.ResetButtonStatus("JoyLStickUp");
- }
- else if (focusedElementIndex < elementCount - 1 && ZInput.GetButtonDown("JoyLStickDown"))
- {
- focusedElement.GiveFocus(false);
- MultiSelectItemListElement newElement = GetElement(focusedElementIndex + 1);
- newElement.GiveFocus(true);
- CenterOnItem(newElement);
- ZInput.ResetButtonStatus("JoyLStickDown");
- }
- else if (ZInput.GetButtonDown("JoyLStickLeft"))
- {
- ZInput.ResetButtonStatus("JoyLStickLeft");
- }
- else if (ZInput.GetButtonDown("JoyLStickRight"))
- {
- ZInput.ResetButtonStatus("JoyLStickRight");
- }
- }
- else if (grid != null)
- {
- int columnCount = grid.constraintCount;
-
- if (focusedElementIndex >= columnCount &&
- ZInput.GetButtonDown("JoyLStickUp"))
- {
- focusedElement.GiveFocus(false);
- MultiSelectItemListElement newElement = GetElement(focusedElementIndex - columnCount);
- newElement.GiveFocus(true);
- CenterOnItem(newElement);
- ZInput.ResetButtonStatus("JoyLStickUp");
- }
- else if (focusedElementIndex < elementCount - columnCount &&
- ZInput.GetButtonDown("JoyLStickDown"))
- {
- focusedElement.GiveFocus(false);
- MultiSelectItemListElement newElement = GetElement(focusedElementIndex + columnCount);
- newElement.GiveFocus(true);
- CenterOnItem(newElement);
- ZInput.ResetButtonStatus("JoyLStickDown");
- }
- else if ((focusedElementIndex % columnCount) > 0 &&
- ZInput.GetButtonDown("JoyLStickLeft"))
- {
- focusedElement.GiveFocus(false);
- MultiSelectItemListElement newElement = GetElement(focusedElementIndex - 1);
- newElement.GiveFocus(true);
- CenterOnItem(newElement);
- ZInput.ResetButtonStatus("JoyLStickLeft");
- }
- else if ((focusedElementIndex % columnCount) < columnCount - 1 &&
- focusedElementIndex < elementCount - 1 &&
- ZInput.GetButtonDown("JoyLStickRight"))
- {
- focusedElement.GiveFocus(false);
- MultiSelectItemListElement newElement = GetElement(focusedElementIndex + 1);
- newElement.GiveFocus(true);
- CenterOnItem(newElement);
- ZInput.ResetButtonStatus("JoyLStickRight");
- }
- }
+ UpdateGamepadNavigation();
if (Multiselect && SelectAllToggle != null)
{
@@ -191,6 +114,90 @@ public void Update()
}
}
+ // Deliberately no ZInput.ResetButtonStatus on the navigation buttons: a reset clears the held state
+ // ZInput's own key repeat runs off, which costs a held stick every repeat past the first.
+ private void UpdateGamepadNavigation()
+ {
+ GridLayoutGroup grid = ListContainer.GetComponent();
+ int columnCount = grid != null ? GetColumnCount(grid) : 1;
+
+ int step;
+ bool horizontal = false;
+ if (ZInput.GetButtonDown("JoyLStickUp"))
+ {
+ step = -columnCount;
+ }
+ else if (ZInput.GetButtonDown("JoyLStickDown"))
+ {
+ step = columnCount;
+ }
+ else if (columnCount > 1 && ZInput.GetButtonDown("JoyLStickLeft"))
+ {
+ step = -1;
+ horizontal = true;
+ }
+ else if (columnCount > 1 && ZInput.GetButtonDown("JoyLStickRight"))
+ {
+ step = 1;
+ horizontal = true;
+ }
+ else
+ {
+ return;
+ }
+
+ List visible = new List();
+ ForeachVisibleElement((_, element) => visible.Add(element));
+ if (visible.Count == 0)
+ {
+ return;
+ }
+
+ MultiSelectItemListElement focusedElement = GetFocusedElement();
+ int currentIndex = focusedElement != null ? visible.IndexOf(focusedElement) : -1;
+ if (currentIndex < 0)
+ {
+ if (focusedElement != null)
+ {
+ focusedElement.GiveFocus(false);
+ }
+
+ visible[0].GiveFocus(true);
+ CenterOnItem(visible[0]);
+ return;
+ }
+
+ int newIndex = currentIndex + step;
+ if (horizontal)
+ {
+ if (newIndex < 0 || newIndex >= visible.Count ||
+ newIndex / columnCount != currentIndex / columnCount)
+ {
+ return;
+ }
+ }
+ else if (newIndex >= visible.Count)
+ {
+ newIndex = visible.Count - 1;
+ }
+
+ if (newIndex < 0 || newIndex == currentIndex)
+ {
+ return;
+ }
+
+ focusedElement.GiveFocus(false);
+ visible[newIndex].GiveFocus(true);
+ CenterOnItem(visible[newIndex]);
+ }
+
+ private static int GetColumnCount(GridLayoutGroup grid)
+ {
+ return grid.constraint == GridLayoutGroup.Constraint.FixedColumnCount
+ ? Mathf.Max(1, grid.constraintCount)
+ : 1;
+ }
+
private void CenterOnItem(MultiSelectItemListElement element)
{
if (_scrollRectEnsureVisible != null)
@@ -720,6 +727,16 @@ public bool IsGrid()
return ListContainer != null && ListContainer.GetComponent() != null;
}
+ public int GetFocusedIndex()
+ {
+ MultiSelectItemListElement focusedElement = GetFocusedElement();
+ return focusedElement != null ? focusedElement.transform.GetSiblingIndex() : -1;
+ }
+
+ public bool ShowSortHint => Sortable && SortByDropdown != null && SortByDropdown.isActiveAndEnabled;
+ public bool ShowSelectAllHint => Multiselect && SelectAllToggle != null && SelectAllToggle.isActiveAndEnabled;
+ public bool ShowSelectHint => !ReadOnly && GetFocusedElement() != null;
+
public void InitWithExistingItems()
{
for (int i = 0; i < ListContainer.childCount; ++i)
diff --git a/EpicLoot/src/UI/Enchanting/MultiSelectItemListElement.cs b/EpicLoot/src/UI/Enchanting/MultiSelectItemListElement.cs
index 0de2a5f29..9d64d9565 100644
--- a/EpicLoot/src/UI/Enchanting/MultiSelectItemListElement.cs
+++ b/EpicLoot/src/UI/Enchanting/MultiSelectItemListElement.cs
@@ -94,23 +94,15 @@ public void Update()
OnClicked();
ZInput.ResetButtonStatus("JoyButtonA");
}
+ // Deliberately no ResetButtonStatus on these two: it clears the held state ZInput's key
+ // repeat runs off, which costs a held d-pad every repeat past the first.
else if (ZInput.GetButtonDown("JoyDPadUp"))
{
SelectQuantity(_selectedQuantity + 1, false);
- ZInput.ResetButtonStatus("JoyDPadUp");
}
else if (ZInput.GetButtonDown("JoyDPadDown"))
{
SelectQuantity(_selectedQuantity - 1, false);
- ZInput.ResetButtonStatus("JoyDPadDown");
- }
- else if (ZInput.GetButtonDown("JoyDPadLeft"))
- {
- ZInput.ResetButtonStatus("JoyDPadLeft");
- }
- else if (ZInput.GetButtonDown("JoyDPadRight"))
- {
- ZInput.ResetButtonStatus("JoyDPadRight");
}
}
@@ -255,7 +247,7 @@ public void SelectQuantity(int quantity, bool noSound)
int prevQuantity = _selectedQuantity;
if (_item == null)
{
- _selectedQuantity = quantity;
+ _selectedQuantity = Mathf.Clamp(quantity, 0, 1);
}
else
{
diff --git a/EpicLoot/src/UI/Enchanting/MultiSelectListFocusController.cs b/EpicLoot/src/UI/Enchanting/MultiSelectListFocusController.cs
index a192c5d7d..c1653faec 100644
--- a/EpicLoot/src/UI/Enchanting/MultiSelectListFocusController.cs
+++ b/EpicLoot/src/UI/Enchanting/MultiSelectListFocusController.cs
@@ -3,6 +3,17 @@
namespace EpicLoot_UnityLib
{
+ public interface IGamepadFocusPane
+ {
+ int GetItemCount();
+ int GetFocusedIndex();
+ void GiveFocus(bool focused, int tryFocusIndex);
+ bool IsGrid();
+ bool ShowSortHint { get; }
+ bool ShowSelectAllHint { get; }
+ bool ShowSelectHint { get; }
+ }
+
public class MultiSelectListFocusController : MonoBehaviour
{
public List Lists = new List();
@@ -10,56 +21,91 @@ public class MultiSelectListFocusController : MonoBehaviour
public GameObject[] SelectAllHints;
public GameObject[] SelectHints;
- private int _focusedListIndex;
+ private readonly List _panes = new List();
+ private List _paneOverride;
+ private int _focusedPaneIndex;
private bool _gamepadWasEnabled;
+ // For panels whose panes are not all MultiSelectItemLists, or that want a different order than the
+ // prefab's. Call it before this component is enabled.
+ public void SetPanes(IEnumerable panes)
+ {
+ _paneOverride = new List(panes);
+ RebuildPanes();
+ ClampFocusedPane();
+ }
public void OnEnable()
{
- _focusedListIndex = 0;
- for (int index = 0; index < Lists.Count; index++)
+ RebuildPanes();
+
+ _focusedPaneIndex = 0;
+ for (int index = 0; index < _panes.Count; index++)
{
- if (Lists[index] == null)
+ _panes[index].GiveFocus(index == _focusedPaneIndex, 0);
+ }
+
+ RefreshHints();
+ }
+
+ private void RebuildPanes()
+ {
+ _panes.Clear();
+
+ if (_paneOverride != null)
+ {
+ foreach (IGamepadFocusPane pane in _paneOverride)
{
- continue;
+ if (pane != null)
+ {
+ _panes.Add(pane);
+ }
}
- Lists[index].GiveFocus(index == _focusedListIndex, 0);
+
+ return;
}
- RefreshHints();
+ foreach (MultiSelectItemList list in Lists)
+ {
+ if (list != null)
+ {
+ _panes.Add(list);
+ }
+ }
+ }
+
+ private void ClampFocusedPane()
+ {
+ _focusedPaneIndex = _panes.Count > 0 ? Mathf.Clamp(_focusedPaneIndex, 0, _panes.Count - 1) : 0;
}
public void Update()
{
- if (Lists.Count == 0)
+ if (_panes.Count == 0)
{
return;
}
- MultiSelectItemList currentList = Lists[_focusedListIndex];
+ ClampFocusedPane();
+
+ IGamepadFocusPane currentPane = _panes[_focusedPaneIndex];
int loopCount = 0;
- int itemCount = currentList.GetItemCount();
- while (itemCount == 0)
+ while (currentPane.GetItemCount() == 0)
{
- currentList.GiveFocus(false, 0);
+ currentPane.GiveFocus(false, 0);
- _focusedListIndex = (_focusedListIndex + 1) % Lists.Count;
- currentList = Lists[_focusedListIndex];
- if (currentList == null)
- {
- continue;
- }
+ _focusedPaneIndex = (_focusedPaneIndex + 1) % _panes.Count;
+ currentPane = _panes[_focusedPaneIndex];
- itemCount = currentList.GetItemCount();
- if (currentList.GetItemCount() > 0)
+ if (currentPane.GetItemCount() > 0)
{
- currentList.GiveFocus(true, 0);
+ currentPane.GiveFocus(true, 0);
RefreshHints();
break;
}
loopCount++;
- if (loopCount >= Lists.Count)
+ if (loopCount >= _panes.Count)
{
return;
}
@@ -67,28 +113,28 @@ public void Update()
if (ZInput.IsGamepadActive())
{
- int newFocusedIndex = _focusedListIndex;
+ int newFocusedIndex = _focusedPaneIndex;
if (ZInput.GetButtonDown("JoyTabLeft"))
{
- newFocusedIndex = Mathf.Max(_focusedListIndex - 1, 0);
+ newFocusedIndex = Mathf.Max(_focusedPaneIndex - 1, 0);
ZInput.ResetButtonStatus("JoyTabLeft");
}
else if (ZInput.GetButtonDown("JoyTabRight"))
{
- newFocusedIndex = Mathf.Min(_focusedListIndex + 1, Lists.Count - 1);
+ newFocusedIndex = Mathf.Min(_focusedPaneIndex + 1, _panes.Count - 1);
ZInput.ResetButtonStatus("JoyTabRight");
}
- if (newFocusedIndex != _focusedListIndex)
+ if (newFocusedIndex != _focusedPaneIndex)
{
- int offset = newFocusedIndex - _focusedListIndex;
- if (Lists[newFocusedIndex].GetItemCount() == 0)
+ int offset = newFocusedIndex - _focusedPaneIndex;
+ if (_panes[newFocusedIndex].GetItemCount() == 0)
{
- newFocusedIndex = (newFocusedIndex + offset + Lists.Count) % Lists.Count;
+ newFocusedIndex = (newFocusedIndex + offset + _panes.Count) % _panes.Count;
}
- if (Lists[newFocusedIndex].GetItemCount() == 0)
+ if (_panes[newFocusedIndex].GetItemCount() == 0)
{
- newFocusedIndex = _focusedListIndex;
+ newFocusedIndex = _focusedPaneIndex;
}
}
@@ -105,16 +151,14 @@ public void Update()
public void FocusList(int newFocusedIndex)
{
- MultiSelectItemList list = Lists[_focusedListIndex];
- MultiSelectItemListElement currentFocusElement = list.GetFocusedElement();
- int currentFocusIndex = currentFocusElement != null ? currentFocusElement.transform.GetSiblingIndex() : -1;
- if (newFocusedIndex != _focusedListIndex && newFocusedIndex >= 0 && newFocusedIndex < Lists.Count)
+ int currentFocusIndex = _panes[_focusedPaneIndex].GetFocusedIndex();
+ if (newFocusedIndex != _focusedPaneIndex && newFocusedIndex >= 0 && newFocusedIndex < _panes.Count)
{
- _focusedListIndex = newFocusedIndex;
- for (int index = 0; index < Lists.Count; index++)
+ _focusedPaneIndex = newFocusedIndex;
+ for (int index = 0; index < _panes.Count; index++)
{
- bool isGrid = Lists[index].IsGrid();
- Lists[index].GiveFocus(index == _focusedListIndex, isGrid ? 0 : currentFocusIndex);
+ bool isGrid = _panes[index].IsGrid();
+ _panes[index].GiveFocus(index == _focusedPaneIndex, isGrid ? 0 : currentFocusIndex);
}
RefreshHints();
@@ -123,23 +167,21 @@ public void FocusList(int newFocusedIndex)
private void RefreshHints()
{
- if (!isActiveAndEnabled || !ZInput.IsGamepadActive() || Lists.Count == 0)
+ if (!isActiveAndEnabled || !ZInput.IsGamepadActive() || _panes.Count == 0)
return;
- MultiSelectItemList focusedList = Lists[_focusedListIndex];
+ IGamepadFocusPane focusedPane = _panes[_focusedPaneIndex];
foreach (GameObject hint in SortHints)
{
- hint.SetActive(focusedList.Sortable && focusedList.SortByDropdown != null &&
- focusedList.SortByDropdown.isActiveAndEnabled);
+ hint.SetActive(focusedPane.ShowSortHint);
}
foreach (GameObject hint in SelectAllHints)
{
- hint.SetActive(focusedList.Multiselect && focusedList.SelectAllToggle != null &&
- focusedList.SelectAllToggle.isActiveAndEnabled);
+ hint.SetActive(focusedPane.ShowSelectAllHint);
}
foreach (GameObject hint in SelectHints)
{
- hint.SetActive(!focusedList.ReadOnly && focusedList.GetFocusedElement() != null);
+ hint.SetActive(focusedPane.ShowSelectHint);
}
}
}
diff --git a/EpicLoot/src/UI/Enchanting/RuneUI.cs b/EpicLoot/src/UI/Enchanting/RuneUI.cs
index 61add1c41..3b78d10d4 100644
--- a/EpicLoot/src/UI/Enchanting/RuneUI.cs
+++ b/EpicLoot/src/UI/Enchanting/RuneUI.cs
@@ -5,6 +5,7 @@
using EpicLoot;
using EpicLoot.CraftingV2;
using UnityEngine;
+using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace EpicLoot_UnityLib
@@ -29,6 +30,9 @@ public class RuneUI : EnchantingTableUIPanelBase
public AudioClip RunicActionCompleted;
+ private readonly List _enchantmentRows = new List();
+ private EnchantmentColumn _enchantmentColumn;
+ private GameObject _rowFocusTemplate;
private RuneAction _runeAction;
private GameObject _successDialog;
private ItemDrop.ItemData _selectedItem;
@@ -42,8 +46,101 @@ private enum RuneAction
Etch
}
+ private class EnchantmentRow
+ {
+ public Toggle Toggle;
+ public GameObject FocusGlow;
+ public RowHover Hover;
+ // False for an effect the rune tab may not touch (CanBeRunified off, or no definition), so
+ // Unlock knows to leave that row disabled.
+ public bool Selectable;
+ }
+
+ private class RowHover : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
+ {
+ public bool Hovered;
+
+ public void OnPointerEnter(PointerEventData eventData)
+ {
+ Hovered = true;
+ }
+
+ public void OnPointerExit(PointerEventData eventData)
+ {
+ Hovered = false;
+ }
+ }
+
+ // The enchantment rows are plain toggles with no navigation and no focus visuals of their own, so
+ // the column rides the bumper rotation as a pane of its own, between the item list and the runes.
+ private class EnchantmentColumn : IGamepadFocusPane
+ {
+ private readonly RuneUI _owner;
+ private bool _focused;
+ private int _focusIndex = -1;
+
+ public EnchantmentColumn(RuneUI owner)
+ {
+ _owner = owner;
+ }
+
+ public int GetItemCount() => _owner._enchantmentRows.Count;
+ public int GetFocusedIndex() => _focusIndex;
+ public bool IsGrid() => false;
+ public bool ShowSortHint => false;
+ public bool ShowSelectAllHint => false;
+ public bool ShowSelectHint => _focusIndex >= 0;
+
+ public void GiveFocus(bool focused, int tryFocusIndex)
+ {
+ _focused = focused;
+ int count = GetItemCount();
+ _focusIndex = focused && count > 0 ? Mathf.Clamp(tryFocusIndex, 0, count - 1) : -1;
+ _owner.RefreshEnchantmentFocus();
+ }
+
+ // The rows are rebuilt whenever the selected item changes, which empties the column for a
+ // moment; without this the pane keeps the rotation's focus but loses its own row, and the
+ // stick and A stop answering until the player bumpers away and back.
+ public void ClampFocus()
+ {
+ int count = GetItemCount();
+ _focusIndex = _focused && count > 0 ? Mathf.Clamp(Mathf.Max(_focusIndex, 0), 0, count - 1) : -1;
+ }
+
+ public void MoveFocus(int step)
+ {
+ int count = GetItemCount();
+ if (_focusIndex < 0 || count == 0)
+ {
+ return;
+ }
+
+ _focusIndex = Mathf.Clamp(_focusIndex + step, 0, count - 1);
+ _owner.RefreshEnchantmentFocus();
+ }
+
+ public void SubmitFocused()
+ {
+ if (_focusIndex < 0 || _focusIndex >= GetItemCount())
+ {
+ return;
+ }
+
+ Toggle toggle = _owner._enchantmentRows[_focusIndex].Toggle;
+ if (toggle != null && toggle.isActiveAndEnabled && toggle.interactable)
+ {
+ // Flipped rather than forced on, so A clears a row the way a click does. The group
+ // allows switching off; where it does not, Unity puts the toggle straight back on.
+ toggle.isOn = !toggle.isOn;
+ }
+ }
+ }
+
public override void Awake()
{
+ _enchantmentColumn = new EnchantmentColumn(this);
+
base.Awake();
RuneExtractButton.onValueChanged.AddListener((isOn) =>
@@ -57,13 +154,37 @@ public override void Awake()
});
AvailableRunes.OnSelectedItemsChanged += OnSelectedOverrideRuneChanged;
+
+ _rowFocusTemplate = AvailableRunes != null && AvailableRunes.ElementPrefab != null
+ ? AvailableRunes.ElementPrefab.GamepadFocusIndicator
+ : null;
+
+ MultiSelectListFocusController focusController = GetComponent();
+ if (focusController != null)
+ {
+ // Visual order, minus the cost list the prefab includes: it is read-only, so a stop there
+ // only costs the player a bumper press.
+ focusController.SetPanes(new IGamepadFocusPane[] { AvailableItems, _enchantmentColumn, AvailableRunes });
+ }
+
+ HideStrayModeHint();
+ }
+
+ // Cloned from the enchant tab, which parks a Y glyph beside its rarity column. Here it lands on top
+ // of the mode selectors' own Y glyph -- two stacked glyphs, and nothing answers this one.
+ private void HideStrayModeHint()
+ {
+ Transform strayHint = transform.Find("GamepadHints/Hint");
+ if (strayHint != null)
+ {
+ strayHint.gameObject.SetActive(false);
+ }
}
[UsedImplicitly]
public void OnEnable()
{
RuneExtractButton.isOn = false;
- RuneEtchButton.Select();
RuneEtchButton.isOn = true;
EtchModeSelected(true);
}
@@ -93,6 +214,126 @@ public override void Update()
Destroy(_successDialog);
_successDialog = null;
}
+
+ if (ZInput.IsGamepadActive())
+ {
+ ClearToggleUISelection();
+ }
+
+ if (!_locked && ZInput.IsGamepadActive())
+ {
+ UpdateEnchantmentColumnInput();
+ }
+
+ RefreshEnchantmentFocus();
+
+ if (!_locked && ZInput.IsGamepadActive() && ZInput.GetButtonDown("JoyButtonY"))
+ {
+ ZInput.ResetButtonStatus("JoyButtonY");
+
+ // Named rather than cycled through the ToggleGroup: it also holds ModeImbueButton, which the
+ // prefab ships deactivated.
+ if (_runeAction == RuneAction.Etch)
+ {
+ RuneExtractButton.isOn = true;
+ }
+ else
+ {
+ RuneEtchButton.isOn = true;
+ }
+ }
+ }
+
+ // The gamepad's A is bound to Unity's Submit axis as well as to JoyButtonA, so any toggle left
+ // selected in the EventSystem re-fires the moment the player presses A elsewhere in the panel and
+ // drags the mode or the enchantment back. Every toggle here is driven by Y or by the enchantment
+ // column's own focus, never by EventSystem navigation.
+ private void ClearToggleUISelection()
+ {
+ EventSystem eventSystem = EventSystem.current;
+ if (eventSystem == null)
+ {
+ return;
+ }
+
+ GameObject selected = eventSystem.currentSelectedGameObject;
+ if (selected != null && selected.transform.IsChildOf(transform) &&
+ selected.GetComponent() != null)
+ {
+ eventSystem.SetSelectedGameObject(null);
+ }
+ }
+
+ private void UpdateEnchantmentColumnInput()
+ {
+ if (_enchantmentColumn.GetFocusedIndex() < 0)
+ {
+ return;
+ }
+
+ // Deliberately no ZInput.ResetButtonStatus on the stick: a reset clears the held state ZInput's
+ // own key repeat runs off, which costs a held stick every repeat past the first.
+ if (ZInput.GetButtonDown("JoyLStickUp"))
+ {
+ _enchantmentColumn.MoveFocus(-1);
+ }
+ else if (ZInput.GetButtonDown("JoyLStickDown"))
+ {
+ _enchantmentColumn.MoveFocus(1);
+ }
+ else if (ZInput.GetButtonDown("JoyButtonA"))
+ {
+ ZInput.ResetButtonStatus("JoyButtonA");
+ _enchantmentColumn.SubmitFocused();
+ }
+ }
+
+ private void RefreshEnchantmentFocus()
+ {
+ int focusIndex = _enchantmentColumn.GetFocusedIndex();
+ bool gamepadActive = ZInput.IsGamepadActive();
+
+ for (int index = 0; index < _enchantmentRows.Count; ++index)
+ {
+ EnchantmentRow row = _enchantmentRows[index];
+ if (row.FocusGlow == null)
+ {
+ continue;
+ }
+
+ bool highlighted = gamepadActive
+ ? index == focusIndex
+ : row.Hover != null && row.Hover.Hovered;
+
+ if (row.FocusGlow.activeSelf != highlighted)
+ {
+ row.FocusGlow.SetActive(highlighted);
+ }
+ }
+ }
+
+ // The rows the prefab ships carry no highlight of their own -- the toggle only tints its own
+ // checkbox -- so a focused row looked no different from the rest. This is the same glow the item
+ // and rune rows use, borrowed off their element prefab.
+ private GameObject CreateRowFocusGlow(Transform row)
+ {
+ if (_rowFocusTemplate == null)
+ {
+ return null;
+ }
+
+ GameObject glow = Instantiate(_rowFocusTemplate, row, false);
+ glow.name = "Focused";
+ glow.transform.SetAsFirstSibling();
+
+ Image glowImage = glow.GetComponent();
+ if (glowImage != null)
+ {
+ glowImage.raycastTarget = false;
+ }
+
+ glow.SetActive(false);
+ return glow;
}
public void UpdateDisplaySelectedItemEnchantments()
@@ -159,6 +400,8 @@ private void ClearEnchantmentList()
Destroy(child.gameObject);
}
}
+ _enchantmentRows.Clear();
+ _enchantmentColumn.ClampFocus();
_selectedEnchantmentIndex = -1;
}
@@ -170,11 +413,12 @@ private void RefreshSelectableEnchantments()
ClearEnchantmentList();
- int enchantIndex = 0;
foreach (Tuple effect in augmentableEffects)
{
GameObject enchantmentListElement = Instantiate(EnchantmentListPrefab, EnchantList);
- Text enchantmentElement = enchantmentListElement.GetComponentInChildren();
+ // Include inactive: the prefab ships deactivated, so the clone is still inactive here and
+ // the plain overload would hand back null and leave every row reading "Enchant Selector".
+ Text enchantmentElement = enchantmentListElement.GetComponentInChildren(true);
Toggle enchantmentbutton = enchantmentListElement.GetComponent();
enchantmentbutton.onValueChanged.AddListener((isOn) =>
{
@@ -188,24 +432,37 @@ private void RefreshSelectableEnchantments()
enchantmentElement.text = effect.Item1;
}
+ // Dimming the row was all that marked an effect with CanBeRunified off, and it could
+ // still be selected, extracted and overwritten. Same treatment as the augment tab.
+ bool selectable = effect.Item2;
+ enchantmentbutton.interactable = selectable && !_locked;
+
enchantmentListElement.SetActive(true);
- enchantIndex++;
+
+ _enchantmentRows.Add(new EnchantmentRow
+ {
+ Toggle = enchantmentbutton,
+ FocusGlow = CreateRowFocusGlow(enchantmentListElement.transform),
+ Hover = enchantmentListElement.AddComponent(),
+ Selectable = selectable
+ });
}
+
+ _enchantmentColumn.ClampFocus();
+ RefreshEnchantmentFocus();
}
+ // Indexed off the tracked rows, not off EnchantList: a rebuild leaves the previous rows parented
+ // there until their Destroy lands at the end of the frame, which shifts every index along.
private void SetSelectedEnchantIndex()
{
- if (EnchantList.childCount > 0)
+ for (int index = 0; index < _enchantmentRows.Count; ++index)
{
- int index = 0;
- foreach (Transform child in EnchantList)
+ Toggle toggle = _enchantmentRows[index].Toggle;
+ if (toggle != null && toggle.isOn)
{
- if (child.GetComponent().isOn == true)
- {
- _selectedEnchantmentIndex = index;
- return;
- }
- index++;
+ _selectedEnchantmentIndex = index;
+ return;
}
}
@@ -244,7 +501,7 @@ public bool LocalPlayerCanAffordRuneCost(List cost)
public void ExtractModeSelected(bool enabled)
{
_runeAction = RuneAction.Extract;
- MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_rune_extract");
+ RefreshMainButtonLabel();
Warning.text = Localization.instance.Localize(EnchantingUIController.GetRuneExtractWarningKey());
// Deselect runes and clear them
@@ -260,7 +517,7 @@ public void ExtractModeSelected(bool enabled)
public void EtchModeSelected(bool enabled)
{
_runeAction = RuneAction.Etch;
- MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_rune_etch");
+ RefreshMainButtonLabel();
Warning.text = Localization.instance.Localize("$mod_epicloot_rune_etch_warning");
AvailableRunesWindow.SetActive(true);
@@ -327,80 +584,207 @@ protected override void DoMainAction()
float powerModifier = GetPowerModifier(featureValues.Item2);
ItemDrop.ItemData item = selectedItem.Item1.GetItem();
- if (_runeAction == RuneAction.Extract)
+ // Everything below acts on the selection as it stands when the countdown ends, so check it
+ // still describes an item the player holds and an effect the rune tab may touch (and the
+ // one the cost was shown for).
+ if (item != _selectedItem || !InventoryManagement.Instance.GetAllItems().Contains(item) ||
+ !EnchantingUIController.CanRunifyEffect(item.GetMagicItem(), _selectedEnchantmentIndex))
{
- List cost = EnchantingUIController.GetRuneExtractCost(item, _selectedRarity, costReduction);
- ItemDrop.ItemData RuneWithEnchant = EnchantingUIController.BuildEnchantedRune(item, _selectedEnchantmentIndex, powerModifier);
+ AbortMainAction("the selected item or enchantment is no longer valid");
+ return;
+ }
+
+ bool completed = _runeAction == RuneAction.Extract
+ ? ExtractSelectedEnchantment(item, costReduction, powerModifier)
+ : _runeAction == RuneAction.Etch && EtchSelectedRune(item, costReduction);
+ if (!completed)
+ {
+ return;
+ }
+
+ DeselectAll();
+
+ RefreshAvailableItems();
+ _selectedEnchantmentIndex = -1;
+ CostList.SetItems(new List());
+ AvailableRunes.SetItems(new List());
+ }
- if (RuneWithEnchant == null)
+ private bool ExtractSelectedEnchantment(ItemDrop.ItemData item, float costReduction, float powerModifier)
+ {
+ List cost = EnchantingUIController.GetRuneExtractCost(item, _selectedRarity, costReduction);
+ ItemDrop.ItemData RuneWithEnchant = EnchantingUIController.BuildEnchantedRune(item, _selectedEnchantmentIndex, powerModifier);
+
+ if (RuneWithEnchant == null)
+ {
+ AbortMainAction("the rune could not be built");
+ return false;
+ }
+
+ Player player = Player.m_localPlayer;
+ bool noCost = player.NoCostCheat();
+ if (!noCost && !LocalPlayerCanAffordCost(cost))
+ {
+ AbortMainAction("the cost can no longer be paid", missingRequirements: true);
+ return false;
+ }
+
+ RuneExtractMode mode = EnchantingUIController.GetRuneExtractMode();
+ List reclaimedSockets = null;
+ if (mode == RuneExtractMode.DestroyItem)
+ {
+ // Socketed stones are the player's property: the non-Locked ones are handed back once
+ // the item is gone, the same policy disenchanting uses. Read before it goes.
+ if (item.IsMagic(out MagicItem extractedMagicItem) && extractedMagicItem.Sockets.Count > 0)
{
- return;
+ reclaimedSockets = EnchantingUIController.ReclaimSockets(extractedMagicItem);
+ }
+
+ // Vanilla never auto-unequips a removed item: destroying an equipped piece (listed when
+ // ShowEquippedAndHotbarItemsInSacrificeTab is on) would leave its stats and visuals.
+ if (player.IsItemEquiped(item))
+ {
+ player.UnequipItem(item, false);
}
- Player player = Player.m_localPlayer;
- if (!player.NoCostCheat())
+ // Taken before anything is charged or handed out, so an item that could not be
+ // removed costs nothing and yields nothing.
+ if (InventoryManagement.Instance.RemoveExactItem(item, 1) < 1)
{
- if (!LocalPlayerCanAffordCost(cost))
- {
- return;
- }
-
- foreach (InventoryItemListElement costElement in cost)
- {
- InventoryManagement.Instance.RemoveItem(costElement.GetItem());
- }
+ AbortMainAction("the item could not be removed");
+ return false;
}
+ }
- // Apply the configured effect to the source item. The rune was already built above.
- switch (EnchantingUIController.GetRuneExtractMode())
+ if (!noCost)
+ {
+ foreach (InventoryItemListElement costElement in cost)
{
- case RuneExtractMode.KeepItem:
- // Item returned untouched.
- break;
- case RuneExtractMode.ReduceEnchants:
- EnchantingUIController.ReduceItemAfterRuneExtract(item, _selectedEnchantmentIndex, reduceRarity: false);
- break;
- case RuneExtractMode.ReduceEnchantsAndRarity:
- EnchantingUIController.ReduceItemAfterRuneExtract(item, _selectedEnchantmentIndex, reduceRarity: true);
- break;
- case RuneExtractMode.DestroyItem:
- // Socketed stones are the player's property: hand the non-Locked ones back
- // before the item is destroyed, the same policy disenchanting uses.
- if (item.IsMagic(out MagicItem extractedMagicItem) && extractedMagicItem.Sockets.Count > 0)
- {
- GiveItemsToPlayer(EnchantingUIController.ReclaimSockets(extractedMagicItem));
- }
- InventoryManagement.Instance.RemoveExactItem(item, 1);
- break;
+ InventoryManagement.Instance.RemoveItem(costElement.GetItem());
}
+ }
- InventoryManagement.Instance.GiveItem(RuneWithEnchant);
- CostList.SetItems(new List());
+ // Apply the configured effect to the source item. The rune was already built above.
+ switch (mode)
+ {
+ case RuneExtractMode.KeepItem:
+ case RuneExtractMode.DestroyItem:
+ // Item returned untouched, or already removed above.
+ break;
+ case RuneExtractMode.ReduceEnchants:
+ EnchantingUIController.ReduceItemAfterRuneExtract(item, _selectedEnchantmentIndex, reduceRarity: false);
+ break;
+ case RuneExtractMode.ReduceEnchantsAndRarity:
+ EnchantingUIController.ReduceItemAfterRuneExtract(item, _selectedEnchantmentIndex, reduceRarity: true);
+ break;
}
- else if (_runeAction == RuneAction.Etch)
+
+ if (reclaimedSockets != null)
+ {
+ GiveItemsToPlayer(reclaimedSockets);
+ }
+
+ InventoryManagement.Instance.GiveItem(RuneWithEnchant);
+ return true;
+ }
+
+ // Modifies the existing item and consumes the selected rune. The etch is worked out on a copy
+ // first; the rune and then the cost are taken, and only then is the result written to the item,
+ // so a failure at any step leaves the item, the rune and the materials where they were.
+ private bool EtchSelectedRune(ItemDrop.ItemData item, float costReduction)
+ {
+ ItemDrop.ItemData rune = AvailableRunes.GetSingleSelectedItem()?.Item1.GetItem();
+ string targetEffect = EnchantingUIController.GetSelectedEnchantmentNameByIndex(item, _selectedEnchantmentIndex);
+ if (rune == null || rune == item || !InventoryManagement.Instance.GetAllItems().Contains(rune) ||
+ !EnchantingUIController.GetApplyableRunesforItem(item, targetEffect).Any(x => x.GetItem() == rune))
+ {
+ AbortMainAction("the selected rune is no longer available for this enchantment");
+ return false;
+ }
+
+ // The same cost CheckIfActionDoable showed and gated the button on.
+ List cost = EnchantingUIController.GetRuneEtchCost(item, _selectedRarity, costReduction);
+ bool noCost = Player.m_localPlayer.NoCostCheat();
+ if (!noCost && !LocalPlayerCanAffordCost(cost))
{
- // Modify an existing item and destroy the selected Rune
- ItemDrop.ItemData rune = AvailableRunes.GetSingleSelectedItem().Item1.GetItem();
- ItemDrop.ItemData itemToEtch = selectedItem?.Item1.GetItem();
+ AbortMainAction("the cost can no longer be paid", missingRequirements: true);
+ return false;
+ }
+
+ MagicItem etched = EnchantingUIController.BuildRuneEtchResult(item, rune, _selectedEnchantmentIndex);
+ if (etched == null)
+ {
+ AbortMainAction("the etch could not be applied");
+ return false;
+ }
- if (_successDialog != null)
+ // The rune first: the cost is only taken once the rune is actually gone.
+ if (InventoryManagement.Instance.RemoveExactItem(rune, 1) < 1)
+ {
+ AbortMainAction("the rune could not be removed");
+ return false;
+ }
+
+ if (!noCost)
+ {
+ foreach (InventoryItemListElement costElement in cost)
{
- Destroy(_successDialog);
+ InventoryManagement.Instance.RemoveItem(costElement.GetItem());
}
+ }
- _successDialog = EnchantingUIController.RuneEnhanceItemAndReturnSuccess(itemToEtch, rune, _selectedEnchantmentIndex);
- _successDialog.SetActive(true);
- // Remove the rune from the inventory
- InventoryManagement.Instance.RemoveExactItem(rune, 1);
- CostList.SetItems(new List());
+ EnchantingUIController.ApplyRuneEtch(item, etched);
+
+ if (_successDialog != null)
+ {
+ Destroy(_successDialog);
}
- DeselectAll();
+ _successDialog = EnchantingUIController.ShowRuneEtchSuccessDialog(item);
+ _successDialog.SetActive(true);
+ return true;
+ }
+ // A main action that could not go ahead: nothing was taken or changed. The panel is already
+ // unlocked (DoMainAction cancels first); rebuild the lists so they show what is really there
+ // now, and let the button state follow the fresh selection.
+ private void AbortMainAction(string reason, bool missingRequirements = false)
+ {
+ Debug.LogWarning($"[Rune] {(_runeAction == RuneAction.Etch ? "Etch" : "Extract")} cancelled: {reason}.");
+ if (missingRequirements)
+ {
+ Player.m_localPlayer?.Message(MessageHud.MessageType.Center, "$msg_missingrequirement");
+ }
RefreshAvailableItems();
_selectedEnchantmentIndex = -1;
CostList.SetItems(new List());
AvailableRunes.SetItems(new List());
+ CheckIfActionDoable();
+ }
+
+ // base.Cancel restores _defaultButtonLabelText, which Awake captured from the prefab's shipped
+ // "$mod_epicloot_rune_slot" label, so every finished action relabelled the button "Apply Rune".
+ private void RefreshMainButtonLabel()
+ {
+ SetMainButtonLabel(_runeAction == RuneAction.Extract
+ ? "$mod_epicloot_rune_extract"
+ : "$mod_epicloot_rune_etch");
+ }
+
+ private void SetMainButtonLabel(string token)
+ {
+ string text = Localization.instance.Localize(token);
+ if (_useTMP)
+ {
+ if (_tmpButtonLabel != null)
+ {
+ _tmpButtonLabel.text = text;
+ }
+ }
+ else if (_buttonLabel != null)
+ {
+ _buttonLabel.text = text;
+ }
}
protected override AudioClip GetCompleteAudioClip()
@@ -464,7 +848,8 @@ private void CheckIfActionDoable()
{
bool state = true;
- if (_selectedItem == null || _selectedEnchantmentIndex == -1)
+ if (_selectedItem == null || _selectedEnchantmentIndex == -1 ||
+ !EnchantingUIController.CanRunifyEffect(_selectedItem.GetMagicItem(), _selectedEnchantmentIndex))
{
state = false;
MainButton.interactable = false;
@@ -515,6 +900,7 @@ public override bool CanCancel()
public override void Cancel()
{
base.Cancel();
+ RefreshMainButtonLabel();
if (_successDialog != null && _successDialog.activeSelf)
{
@@ -530,6 +916,16 @@ public override void Lock()
RuneExtractButton.interactable = false;
RuneEtchButton.interactable = false;
MainButton.interactable = false;
+
+ // The countdown acts on _selectedEnchantmentIndex when it ends; changing the row mid-way
+ // used to etch or extract a different effect from the one the cost was shown for.
+ foreach (EnchantmentRow row in _enchantmentRows)
+ {
+ if (row.Toggle != null)
+ {
+ row.Toggle.interactable = false;
+ }
+ }
}
public override void Unlock()
@@ -538,6 +934,14 @@ public override void Unlock()
RuneExtractButton.interactable = true;
RuneEtchButton.interactable = true;
+
+ foreach (EnchantmentRow row in _enchantmentRows)
+ {
+ if (row.Toggle != null)
+ {
+ row.Toggle.interactable = row.Selectable;
+ }
+ }
}
public override void DeselectAll()
diff --git a/EpicLoot/src/UI/Enchanting/SacrificeUI.cs b/EpicLoot/src/UI/Enchanting/SacrificeUI.cs
index c003cd521..ac586f839 100644
--- a/EpicLoot/src/UI/Enchanting/SacrificeUI.cs
+++ b/EpicLoot/src/UI/Enchanting/SacrificeUI.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using EpicLoot;
+using EpicLoot.Crafting;
using EpicLoot.CraftingV2;
using JetBrains.Annotations;
using UnityEngine;
@@ -33,10 +34,14 @@ enum SacrificeMode
SacrificeMode _sacrificeMode = SacrificeMode.Sacrifice;
+ private readonly List _identifyCategoryHints = new List();
+
public override void Awake()
{
base.Awake();
+ CreateIdentifyCategoryHint();
+
SacrificeToggle.onValueChanged.AddListener((isOn) => {
SacrificeModeSelected(isOn);
});
@@ -65,6 +70,7 @@ public void OnEnable()
List items = EnchantingUIController.GetSacrificeItems();
_sacrificeMode = SacrificeMode.Sacrifice;
IdentifyStylePanel.SetActive(false);
+ ShowIdentifyCategoryHint(false);
IdentifyToggle.isOn = false;
SacrificeToggle.isOn = true;
AvailableItems.ClearFilter();
@@ -72,6 +78,97 @@ public void OnEnable()
AvailableItems.DeselectAll();
}
+ // The row's glyphs are fixed sprites, and the bundle ships only an up/down d-pad: the clone wears
+ // the same one turned on its side.
+ private void CreateIdentifyCategoryHint()
+ {
+ Transform bottomRow = transform.Find("GamepadHints/BottomRow");
+ if (bottomRow == null)
+ {
+ return;
+ }
+
+ Transform spacing = bottomRow.Find("Spacing");
+ Transform label = bottomRow.Find("Quantity");
+ Transform glyph = bottomRow.Find("QuantityButton");
+ if (spacing == null || label == null || glyph == null)
+ {
+ return;
+ }
+
+ GameObject spacingClone = Instantiate(spacing.gameObject, bottomRow, false);
+ spacingClone.name = "Spacing";
+
+ GameObject labelClone = Instantiate(label.gameObject, bottomRow, false);
+ labelClone.name = "IdentifyCategory";
+ Text labelText = labelClone.GetComponentInChildren(true);
+ if (labelText != null)
+ {
+ labelText.text = Localization.instance.Localize("$mod_epicloot_enchanting_identifycategory");
+ }
+
+ GameObject glyphClone = Instantiate(glyph.gameObject, bottomRow, false);
+ glyphClone.name = "IdentifyCategoryButton";
+ Transform icon = glyphClone.transform.Find("Icon");
+ if (icon != null)
+ {
+ icon.localEulerAngles = new Vector3(0f, 0f, 90f);
+ }
+
+ _identifyCategoryHints.Add(spacingClone);
+ _identifyCategoryHints.Add(labelClone);
+ _identifyCategoryHints.Add(glyphClone);
+ ShowIdentifyCategoryHint(false);
+ }
+
+ private void ShowIdentifyCategoryHint(bool visible)
+ {
+ foreach (GameObject hint in _identifyCategoryHints)
+ {
+ if (hint != null && hint.activeSelf != visible)
+ {
+ hint.SetActive(visible);
+ }
+ }
+ }
+
+ protected override void OnDPadHorizontal(int direction)
+ {
+ if (_locked || _sacrificeMode != SacrificeMode.Identify)
+ {
+ return;
+ }
+
+ int optionCount = IdentifyStyle.options.Count;
+ if (optionCount == 0)
+ {
+ return;
+ }
+
+ IdentifyStyle.value = (IdentifyStyle.value + direction + optionCount) % optionCount;
+ }
+
+ public override void Update()
+ {
+ base.Update();
+
+ if (_locked || !ZInput.IsGamepadActive() || !ZInput.GetButtonDown("JoyButtonY"))
+ {
+ return;
+ }
+
+ ZInput.ResetButtonStatus("JoyButtonY");
+
+ if (_sacrificeMode == SacrificeMode.Sacrifice)
+ {
+ IdentifyToggle.isOn = true;
+ }
+ else
+ {
+ SacrificeToggle.isOn = true;
+ }
+ }
+
protected override void DoMainAction()
{
if (_sacrificeMode == SacrificeMode.Identify)
@@ -98,20 +195,18 @@ private void IdentifyItems()
float powerModifier = GetPowerModifier(featureValues.Item2);
List cost = EnchantingUIController.GetIdentifyCostForCategory(filterType, unidentifiedItems, costReduction);
+ // Cancel on every way out: returning without it left the button reading "Cancel".
if (!LocalPlayerCanAffordCost(cost))
{
+ Player.m_localPlayer.Message(MessageHud.MessageType.Center, "$msg_missingrequirement");
+ Cancel();
+ RefreshAvailableItems();
return;
}
- if (!Player.m_localPlayer.NoCostCheat())
- {
- foreach (InventoryItemListElement costElement in cost)
- {
- InventoryManagement.Instance.RemoveItem(costElement.GetItem());
- }
- }
-
- List identifiedItems = EnchantingUIController.LootRollSelectedItems(filterType, unidentifiedItems, powerModifier);
+ // Rolls, re-checks the cost, charges it, removes the inputs and hands the results over --
+ // or, if any stack cannot be identified in full, does none of that.
+ EnchantingUIController.LootRollSelectedItems(filterType, unidentifiedItems, costReduction, powerModifier);
Cancel();
RefreshAvailableItems();
@@ -131,16 +226,61 @@ private float GetPowerModifier(float value)
private void SacrificeItems()
{
List> selectedItems = AvailableItems.GetSelectedItems();
- List sacrificeProducts = EnchantingUIController.GetSacrificeProducts(selectedItems
- .Select(x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList());
Cancel();
+ // Remove first, pay out after: the products and the returned socket stones follow from what
+ // was actually taken, so an item that is no longer there yields nothing.
+ Player player = Player.m_localPlayer;
+ List> sacrificedItems = new List>();
+ List reclaimedSockets = new List();
+ foreach (Tuple selectedItem in selectedItems)
+ {
+ ItemDrop.ItemData sacrificed = selectedItem.Item1.GetItem();
+ int amount = selectedItem.Item2;
+ // Re-asked now rather than trusted from the list: an external filter (API
+ // RegisterSacrificeFilter) may have started vetoing the item during the countdown.
+ if (sacrificed == null || amount <= 0 || EnchantCostsHelper.GetSacrificeProducts(sacrificed) == null)
+ {
+ continue;
+ }
+
+ // Socketed stones are the player's property, not part of the sacrifice yield: the
+ // non-Locked ones go back once the item is gone, the same policy disenchanting uses.
+ // Kept apart from the products so the double-yield bonus never doubles them.
+ List sockets =
+ sacrificed.IsMagic(out MagicItem sacrificedMagicItem) && sacrificedMagicItem.Sockets.Count > 0
+ ? EnchantingUIController.ReclaimSockets(sacrificedMagicItem)
+ : null;
+
+ // Listed while equipped when ShowEquippedAndHotbarItemsInSacrificeTab is on, and vanilla
+ // never auto-unequips a removed item (ghost stats and visuals).
+ if (player != null && player.IsItemEquiped(sacrificed))
+ {
+ player.UnequipItem(sacrificed, false);
+ }
+
+ int removed = InventoryManagement.Instance.RemoveExactItem(sacrificed, amount);
+ if (removed <= 0)
+ {
+ Debug.LogWarning($"[Sacrifice] {sacrificed.m_shared.m_name} could not be removed; skipped.");
+ continue;
+ }
+
+ sacrificedItems.Add(new Tuple(sacrificed, removed));
+ if (sockets != null)
+ {
+ reclaimedSockets.AddRange(sockets);
+ }
+ }
+
+ List sacrificeProducts = EnchantingUIController.GetSacrificeProducts(sacrificedItems);
+
Tuple chanceToDoubleEntry =
EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Sacrifice);
float chanceToDouble = float.IsNaN(chanceToDoubleEntry.Item1) ? 0.0f : chanceToDoubleEntry.Item1 / 100.0f;
- if (Random.Range(0.0f, 1.0f) < chanceToDouble)
+ if (sacrificeProducts.Count > 0 && Random.Range(0.0f, 1.0f) < chanceToDouble)
{
EnchantingTableUI.instance.PlayEnchantBonusSFX();
BonusPanel.Show();
@@ -151,20 +291,7 @@ private void SacrificeItems()
}
}
- foreach (Tuple selectedItem in selectedItems)
- {
- // Socketed stones are the player's property, not part of the sacrifice yield: hand the
- // non-Locked ones back before the item is destroyed, the same policy disenchanting uses.
- // Given separately from sacrificeProducts so the double-yield bonus never doubles them.
- ItemDrop.ItemData sacrificed = selectedItem.Item1.GetItem();
- if (sacrificed.IsMagic(out MagicItem sacrificedMagicItem) && sacrificedMagicItem.Sockets.Count > 0)
- {
- GiveItemsToPlayer(EnchantingUIController.ReclaimSockets(sacrificedMagicItem));
- }
-
- InventoryManagement.Instance.RemoveExactItem(sacrificed, selectedItem.Item2);
- }
-
+ GiveItemsToPlayer(reclaimedSockets);
GiveItemsToPlayer(sacrificeProducts);
RefreshAvailableItems();
@@ -188,10 +315,11 @@ private void SacrificeModeSelected(bool isOn)
Warning.text = Localization.instance.Localize("$mod_epicloot_sacrifice_warning");
Warning.color = Color.red;
Explainer.text = Localization.instance.Localize("$mod_epicloot_sacrifice_productsexplainer");
- MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_sacrifice");
+ SetMainButtonLabel("$mod_epicloot_sacrifice");
OnSelectedItemsChanged();
IdentifyStylePanel.SetActive(false);
CostList.gameObject.SetActive(false);
+ ShowIdentifyCategoryHint(false);
}
private void IdentifyModeSelected(bool isOn)
@@ -211,9 +339,10 @@ private void IdentifyModeSelected(bool isOn)
OnSelectedItemsChanged();
Warning.text = Localization.instance.Localize("$mod_epicloot_identify_explain");
Warning.color = new Color(1f, 0.631f, 0.235f);
- MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_identify");
+ SetMainButtonLabel("$mod_epicloot_identify");
IdentifyStylePanel.SetActive(true);
CostList.gameObject.SetActive(true);
+ ShowIdentifyCategoryHint(true);
}
private void RefreshAvailableItems()
@@ -248,7 +377,8 @@ protected override void OnSelectedItemsChanged()
{
string identifyFilter = IdentifyStyle.options[IdentifyStyle.value].text;
List potentialIdentifyItems =
- EnchantingUIController.GetPotentialItemRollsByCategory(identifyFilter, selectedItems.Select(x => x.Item1.GetItem()).ToList());
+ EnchantingUIController.GetPotentialItemRollsByCategory(identifyFilter,
+ selectedItems.Select(x => x.Item1.GetItem()).ToList(), out bool hasIdentifyLoot);
SacrificeProducts.SetItems(potentialIdentifyItems.Cast().ToList());
// Say so when progression gating will identify a selected item below its own biome.
@@ -267,7 +397,10 @@ protected override void OnSelectedItemsChanged()
CostList.SetItems(cost.Cast().ToList());
canAfford = LocalPlayerCanAffordIdentifyCost(cost);
- if (potentialIdentifyItems.Count() == 0)
+ // Whether the loot lists name anything at all, not whether the preview shows anything:
+ // the preview hides entries gating would replace, and the roll still hands out those
+ // replacements.
+ if (!hasIdentifyLoot)
{
canAfford = false;
}
@@ -311,26 +444,31 @@ public bool LocalPlayerCanAffordIdentifyCost(List cost
public override void Cancel()
{
- if (_sacrificeMode == SacrificeMode.Sacrifice)
+ base.Cancel();
+ RefreshMainButtonLabel();
+ }
+
+ private void RefreshMainButtonLabel()
+ {
+ SetMainButtonLabel(_sacrificeMode == SacrificeMode.Identify
+ ? "$mod_epicloot_identify"
+ : "$mod_epicloot_sacrifice");
+ }
+
+ private void SetMainButtonLabel(string token)
+ {
+ string text = Localization.instance.Localize(token);
+ if (_useTMP)
{
- if (_useTMP)
+ if (_tmpButtonLabel != null)
{
- _tmpButtonLabel.text = Localization.instance.Localize("$mod_epicloot_sacrifice");
- }
- else
- {
- _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_sacrifice");
+ _tmpButtonLabel.text = text;
}
}
- if (SacrificeMode.Identify == _sacrificeMode)
+ else if (_buttonLabel != null)
{
- if (_buttonLabel != null)
- {
- _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_identify");
- }
+ _buttonLabel.text = text;
}
-
- Unlock();
}
public override void DeselectAll()
diff --git a/EpicLoot/src/UI/Enchanting/UpgradeTableUI.cs b/EpicLoot/src/UI/Enchanting/UpgradeTableUI.cs
index 3f1b7a4c3..d15512557 100644
--- a/EpicLoot/src/UI/Enchanting/UpgradeTableUI.cs
+++ b/EpicLoot/src/UI/Enchanting/UpgradeTableUI.cs
@@ -35,6 +35,47 @@ public override void Awake()
}
}
+ public override void Update()
+ {
+ base.Update();
+
+ if (_locked || !ZInput.IsGamepadActive())
+ {
+ return;
+ }
+
+ // Deliberately no ZInput.ResetButtonStatus on the navigation buttons: a reset clears the held
+ // state ZInput's own key repeat runs off, which costs a held stick every repeat past the first.
+ int step;
+ if (ZInput.GetButtonDown("JoyLStickUp"))
+ {
+ step = -1;
+ }
+ else if (ZInput.GetButtonDown("JoyLStickDown"))
+ {
+ step = 1;
+ }
+ else
+ {
+ return;
+ }
+
+ List available = GetAvailableButtons();
+ if (available.Count == 0)
+ {
+ return;
+ }
+
+ int currentIndex = available.FindIndex(x => x.HasGamepadFocus());
+ int newIndex = currentIndex < 0 ? 0 : Mathf.Clamp(currentIndex + step, 0, available.Count - 1);
+ if (newIndex == currentIndex)
+ {
+ return;
+ }
+
+ FocusButton(available[newIndex]);
+ }
+
public void OnEnable()
{
if (EnchantingTableUI.instance.SourceTable != null)
@@ -91,6 +132,8 @@ public void Refresh()
button.gameObject.SetActive(featureIsEnabled);
}
+ RefreshGamepadFocus();
+
if (_selectedFeature >= 0)
{
MultiSelectItemListElement selectedButton = _featureButtons[_selectedFeature];
@@ -163,6 +206,31 @@ public void Refresh()
MainButton.interactable = !maxLevel && canAfford;
}
+ private List GetAvailableButtons()
+ {
+ return _featureButtons.Where(x => x.gameObject.activeSelf).ToList();
+ }
+
+ private void RefreshGamepadFocus()
+ {
+ List