Compare commits

...

44 Commits

Author SHA1 Message Date
WinDanesz 255d5add29 feat: Add support for optional NBT tags in currency items for wizard trades in the configs. example values::
minecraft:potion{Potion:"minecraft:strength"} 4
minecraft:enchanted_book{StoredEnchantments:[{id:16s,lvl:1s}]} 5
2026-05-12 01:35:58 +02:00
WinDanesz 032f1aa1a3 fix: Fixed potion effect desyncs in the codebase (lingering clientside-only potion effects with 0 duration) 2026-05-11 16:35:12 +00:00
WinDanesz ba71f2ae4a feat: Prevent crystal flowers from growing on client-side when bonemeal is used on grass. Fixes #907 2026-05-11 02:23:26 +02:00
WinDanesz 84a375c1be chore: update version to 4.3.18 2026-05-11 02:21:00 +02:00
WinDanesz 9a58332bf6 feat: Sneak clicking items place them in bookshelves. Sneak clicking a bookshelf with an empty hand removes items from the bookshelf 2026-05-11 02:00:24 +02:00
WinDanesz 62efc4cb02 feat: Added Ukrainian translation by Lotos 2026-05-10 23:07:08 +02:00
WinDanesz d6f9352940 feat: Add support for external mod predicates in ally designation system, allowing mods to define their own criteria for alliance status 2026-05-09 21:56:38 +02:00
WinDanesz 643ab1a1be feat: Improve owner ally check in AllyDesignationSystem to handle null owners and recursion 2026-05-09 20:40:03 +02:00
WinDanesz f499fcaeff chore: update version to 4.3.17 2026-05-05 23:59:56 +02:00
WinDanesz 80886c11ce Add Gradle build workflow to GitHub Actions 2026-05-05 17:35:55 +02:00
Xvareon 39f8d0ee7e fix: Fixed NPE crash for isAllied() function, causing world deadlocks (#906) 2026-05-05 11:30:20 +02:00
WinDanesz bbe4634535 Some bomb fixes, colored spell book overlay are now separate files 2026-05-05 02:43:22 +02:00
WinDanesz e7b8f7681c Fix ImbuementActivateEvent, fixes #901 2026-04-18 23:45:32 +02:00
ipdnaeip 64168d103d AllyDesignationSystem update
Added isOnSameTeam check to  isValidTarget and isAllied
2026-04-18 23:38:00 +02:00
ipdnaeip 600a321f49 Fixed spelling error in updateImbuedItem, Fixed improper evaluation for command spells causing them to not trigger spell.onFinish 2026-04-18 23:37:38 +02:00
WinDanesz e753f4df32 Fix: Ensure spell properties are initialized before checking if the spell is enabled 2026-04-18 23:34:56 +02:00
WinDanesz cbe7353122 Update version to 4.3.16 2025-12-31 00:37:10 +01:00
WinDanesz de5682a960 Add colorized bookmarks based on book element, requires spells to be discovered. It can be turned off in the Client settings. Another tweak setting can make it depend on the Archivist's Eyeglass artefact 2025-12-30 18:35:35 +01:00
Lumberjacksparrow b451399323 Fix for inventory key not exiting the Arcane Workbench GUI #897 (Lumberjacksparrow) 2025-12-29 22:17:18 +01:00
ipdnaeip e547aa32ca Fix: Modifiers persist in continuous spells cast by scrolls (#895) (ipdnaeip)
* ItemScroll Modifier Fix

Modifiers persist in continuous spells cast by scrolls.

Added check for tick 0 as to not reapply SpellModifier changes

* Fix comment formatting in ItemScroll.java

---------

Co-authored-by: WinDanesz <31292708+WinDanesz@users.noreply.github.com>
2025-12-29 22:16:24 +01:00
WinDanesz 3fe9d762da Added support for item metadata for "currencyItems" settings (List of registry names of items which wizard trades can use as currency) 2025-12-29 22:11:21 +01:00
WinDanesz 20b53596e2 fix: Forfeit sounds are properly registered for add-on forfeits 2025-10-22 23:50:04 +02:00
WinDanesz c04a9fd37b feat: Add config for flesh spells: fleshSpellsCauseSlowness, diamondFleshArmorBonus, diamondFleshArmorToughnessBonus, ironFleshArmorBonus, oakFleshArmorBonus, oakFleshHealthBonus 2025-09-01 01:51:48 +02:00
WinDanesz fe59a3c377 feat: Add configurable condenser amount multiplier
- Add condenserAmountMultiplier setting with default value 1.0
2025-08-31 23:00:01 +02:00
WinDanesz fc9c6bc7e4 feat: Make tier values configurable through Settings, per tier:
- maxCharge (wand mana)
- upgradeLimit (number of possible upgrade scrolls applied to a specific tier, and weight
2025-08-31 22:46:57 +02:00
WinDanesz e3fa7a5eed feat: Added BASE_SPELL_SLOTS config option
Gameplay Category:
- baseSpellSlots: Number of spell slots a wand has with no attunement upgrades applied (default: 5, range: 1-5)
2025-08-31 17:54:02 +02:00
WinDanesz de135a8528 feat: Added new config options:
Gameplay Category:
- manaPerShard: Amount of mana a crystal shard is worth (default: 10)
- manaPerCrystal: Amount of mana each magic crystal is worth (default: 100)
- grandCrystalMana: Amount of mana a grand magic crystal is worth (default: 400)
- nonElementalUpgradeBonus: Bonus wand upgrades for non-elemental wands (default: 3)
- siphonManaPerLevel: Mana gained per siphon upgrade level (default: 5)
- condenserTickInterval: Ticks between condenser mana increases (default: 50)
Tweaks Category:
- storageIncreasePerLevel: Charge increase per storage upgrade level (default: 0.15)
2025-08-31 17:47:39 +02:00
WinDanesz 5ac053ddd9 feat: Regenerating Shrines. The following config options were added:
- 'shrineRegenerationEnabled': Whether conquered shrines should regenerate after a period of time. Regeneration means wizards and loot will spawn again after a while.
      - 'shrineRegenerationTime': Time in minutes for a conquered shrine to regenerate.
      - 'shrineAllowMultipleLoot': Whether players can loot shrines multiple times. If false, each player can only loot each shrine once.
      When shrineRegenerationEnabled, Shrine chests behave a bit differently - upon defeating all wizards, the chest breaks immediately dropping or not dropping loot based on shrineAllowMultipleLoot. When shrineRegenerationEnabled, the shrine's altar block is unbreakable.
2025-08-31 17:39:22 +02:00
WinDanesz f817f06dbe feat: Mind controlled creatures are considered allies by AoE spells. Fixes #890 2025-08-31 14:56:41 +02:00
WinDanesz 5d5acf253b feat: Fireskin burns away cobweb blocks on contact 2025-08-31 14:49:21 +02:00
WinDanesz bb4faf8ec9 fix: All items are searchable in creative search (crystals, dusts, tomes) 2025-08-31 14:26:37 +02:00
WinDanesz 7f9edcfaef feat: Added new config: "recentSpellExpiryTime": The time in ticks after which recent spell casts expire and no longer count toward progression penalties. Default is 1200 ticks (1 minute). Lower values make progression penalties shorter-lived, higher values make them last longer. 2025-08-31 13:58:52 +02:00
WinDanesz 1c1e6d1e00 fix: change Y_OFFSET from protected to public in SpellRay class 2025-06-09 14:26:21 +02:00
WinDanesz 550b600424 feat: add option to allow unfocused search bars in Arcane Workbench and Lectern 2025-06-07 01:34:59 +02:00
WinDanesz 9673a6c0f6 version bump to 4.3.15 2025-06-06 23:13:44 +02:00
WinDanesz 20b56cfcbd fix: GuiContainers can no longer be opened while possessing something. Fixes #888 2025-06-06 23:03:55 +02:00
19 947af8727d Fixed barriers not allowing ally players in (#868)
Had to edit telekinesis because it was had errors do to syntax. See comments in forcefield class for more information

Co-authored-by: WinDanesz <31292708+WinDanesz@users.noreply.github.com>
2025-06-06 22:19:17 +02:00
Yong Jun 23b950d5b8 add Immune to magic damage config option (#887)
* added more config

* Update src/main/java/electroblob/wizardry/Settings.java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-07 23:23:46 +02:00
WinDanesz 2eaa9b445c fix: fix receptacle crash with FluidLoggedAPI. Fixes #875 2025-03-28 22:49:49 +01:00
WinDanesz c166bac323 fix: convertToStatue shouldn't run on the clientside 2025-03-28 22:13:58 +01:00
Suelvys Silva Bezerra 074a429c6a fix. Updated Brazilian Portuguese translation (by Suelvys) (#870) 2025-03-28 22:02:06 +01:00
Heimdallr-1 3fab85e134 fix: Updated Russian translation (by Heimdallr-1) (#873) 2025-03-28 22:01:20 +01:00
Koko Ayame 5aec954061 fix: Fix Korean translation (by Preta-Crowz) (#879)
One bracket was missing and caused crash
2025-03-28 21:59:04 +01:00
ipdnaeip 094a5f615f Bug Fixes and Sixth Sense Visual Improvement (#886) 2025-03-28 21:55:16 +01:00
106 changed files with 7931 additions and 2500 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"homepage": "https://www.curseforge.com/minecraft/mc-mods/electroblobs-wizardry",
"promos": {
"1.12.2-latest": "4.3.14",
"1.12.2-recommended": "4.3.14"
"1.12.2-latest": "4.3.18",
"1.12.2-recommended": "4.3.18"
}
}
+50
View File
@@ -0,0 +1,50 @@
name: Gradle Build
on:
push:
branches: [ "**" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up JDK 8
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'temurin'
- name: Load Cache
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: ${{ github.ref != 'refs/heads/1.12.2' }}
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build --max-workers=1 --no-daemon
- name: Upload build artifacts
if: success()
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: build/libs/*.jar
retention-days: 30
+4 -1
View File
@@ -38,7 +38,7 @@ apply plugin: org.ajoberstar.grgit.gradle.GrgitPlugin
apply plugin: 'wtf.gofancy.fancygradle'
version = "4.3.14"
version = "4.3.18"
group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "ElectroblobsWizardry"
@@ -59,6 +59,8 @@ minecraft {
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
def argsz = ['--username', 'WinDanesz', '--user', 'WinDanesz', '--uuid', '7faee354-8c60-4f5c-9862-fc0ce5f7f575']
runs {
client {
workingDirectory project.file('run')
@@ -68,6 +70,7 @@ minecraft {
// Recommended logging level for the console
property 'forge.logging.console.level', 'info'
args argsz
}
server {
@@ -43,6 +43,10 @@ public class CommonProxy {
public void registerRenderers(){}
public void registerItemColorHandlers(){}
public void registerModelProperties(){}
public void initialiseLayers(){}
public void initialiseAnimations(){}
@@ -175,6 +179,8 @@ public class CommonProxy {
public void handleConquerShrinePacket(PacketConquerShrine.Message message){}
public void handleBombExplosionPacket(PacketBombExplosion.Message message){}
// SECTION Misc
// ===============================================================================================================
+392 -15
View File
@@ -13,6 +13,9 @@ import net.minecraft.entity.EntityList;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.JsonToNBT;
import net.minecraft.nbt.NBTException;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration;
@@ -138,6 +141,12 @@ public final class Settings {
new ResourceLocation(Wizardry.MODID, "shrine_5"),
new ResourceLocation(Wizardry.MODID, "shrine_6"),
new ResourceLocation(Wizardry.MODID, "shrine_7")};
/** <b>[Server-only]</b> Whether conquered shrines should regenerate after a period of time. */
public boolean shrineRegenerationEnabled = true;
/** <b>[Server-only]</b> Time in minutes for a conquered shrine to regenerate. */
public int shrineRegenerationTime = 1;
/** <b>[Server-only]</b> Whether players can loot shrines multiple times. If false, each player can only loot each shrine once. */
public boolean shrineAllowMultipleLoot = false;
/** <b>[Server-only]</b> List of dimension ids in which to generate library ruins. */
public int[] libraryDimensions = {0};
/** <b>[Server-only]</b> The rarity of library ruins, used by the world generator. Larger numbers are rarer. */
@@ -215,6 +224,8 @@ public final class Settings {
public boolean wandsMustBeHeldToDecrementCooldown = false;
/** <b>[Server-only]</b> Whether to enable Wizardry mob loot injection. Allows an easier switch instead of blacklisting all entities. */
public boolean injectMobDrops = true;
/** <b>[Server-only]</b> The time in ticks after which recent spell casts expire and no longer count toward progression penalties. */
public int recentSpellExpiryTime = 1200;
/**
* <b>[Server-only]</b> List of registry names of entities which summoned creatures are allowed to attack, in addition
* to the defaults.
@@ -243,6 +254,8 @@ public final class Settings {
public Pair<ResourceLocation, Short>[] bowItemWhitelist = parseItemMetaStrings();
/** <b>[Server-only]</b> Map of items to values which wizard trades may use as currency. */
public Map<Pair<ResourceLocation, Short>, Integer> currencyItems = new HashMap<>();
/** <b>[Server-only]</b> Map of optional NBT tags for currency items, keyed the same as {@link #currencyItems}. */
public Map<Pair<ResourceLocation, Short>, NBTTagCompound> currencyItemNbt = new HashMap<>();
/** <b>[Server-only]</b> Global damage scaling factor for all player magic damage. */
public double playerDamageScale = 1.0;
/** <b>[Server-only]</b> Global damage scaling factor for all npc magic damage. */
@@ -258,6 +271,26 @@ public final class Settings {
/** <b>[Server-only]</b> List of registry names of biomes in which wizardry's hostile mobs cannot spawn. */
public ResourceLocation[] mobSpawnBiomeBlacklist = toResourceLocations("mushroom_island", "mushroom_island_shore");
// Mana and upgrade constants
/** <b>[Server-only]</b> The amount of mana a crystal shard is worth */
public int manaPerShard = 10;
/** <b>[Server-only]</b> The amount of mana each magic crystal is worth */
public int manaPerCrystal = 100;
/** <b>[Server-only]</b> The amount of mana a grand magic crystal is worth */
public int grandCrystalMana = 400;
/** <b>[Server-only]</b> The maximum number of one type of wand upgrade which can be applied to a wand. */
public int upgradeStackLimit = 3;
/** <b>[Server-only]</b> The bonus amount of wand upgrades that can be applied to a non-elemental wand. */
public int nonElementalUpgradeBonus = 3;
/** <b>[Server-only]</b> The fraction by which maximum charge is increased for each level of storage upgrade. */
public float storageIncreasePerLevel = 0.15f;
/** <b>[Server-only]</b> The amount of mana given for a kill for each level of siphon upgrade. */
public int siphonManaPerLevel = 5;
/** <b>[Server-only]</b> The number of ticks between each mana increase for wands with the condenser upgrade. */
public int condenserTickInterval = 50;
/** <b>[Server-only]</b> The number of spell slots a wand has with no attunement upgrades applied. */
public int baseSpellSlots = 5;
// Commands (these don't need synchronising since typing a command always queries the server).
/**
* <b>[Server-only]</b> The maximum allowed multiplier for the /cast command. This limit is here to stop people from
@@ -327,10 +360,40 @@ public final class Settings {
* velocity-based one.
*/
public boolean replaceVanillaFallDamage = true;
/** <b>[Synchronised]</b> Whether spell book colors are only shown when the player has the Archivist's Eyeglass equipped. */
public boolean spellBookColorsRequireArchivistsEyeglass = false;
/** <b>[Synchronised]</b> Chance of 'misreading' an undiscovered spell and triggering a forfeit instead. */
public double forfeitChance = 0.2;
/** <b>[Synchronised]</b> Progression requirements for upgrading a wand to each tier. */
public int[] progressionRequirements = {1500, 3500, 6000};
// Integer wrappers for tier values
public Integer noviceMaxCharge = 700;
public Integer apprenticeMaxCharge = 1000;
public Integer advancedMaxCharge = 1500;
public Integer masterMaxCharge = 2500;
public Integer noviceUpgradeLimit = 3;
public Integer apprenticeUpgradeLimit = 5;
public Integer advancedUpgradeLimit = 7;
public Integer masterUpgradeLimit = 9;
/** <b>[Synchronised]</b> Multiplier for condenser upgrade mana regeneration amount */
public double condenserAmountMultiplier = 1.0;
/** <b>[Synchronised]</b> Whether flesh spells (DiamondFlesh, IronFlesh, OakFlesh) apply slowness */
public boolean fleshSpellsCauseSlowness = true;
/** <b>[Synchronised]</b> Armor bonus for DiamondFlesh spell */
public double diamondFleshArmorBonus = 4.0;
/** <b>[Synchronised]</b> Armor toughness bonus for DiamondFlesh spell */
public double diamondFleshArmorToughnessBonus = 3.0;
/** <b>[Synchronised]</b> Armor bonus for IronFlesh spell */
public double ironFleshArmorBonus = 4.0;
/** <b>[Synchronised]</b> Armor bonus for OakFlesh spell */
public double oakFleshArmorBonus = 3.0;
/** <b>[Synchronised]</b> Health bonus for OakFlesh spell */
public double oakFleshHealthBonus = 0.2;
/**
* <b>[Synchronised]</b> The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be
* able to link to it.
@@ -397,12 +460,16 @@ public final class Settings {
/** <b>[Client-only]</b> Whether to initialise the handbook's data. Setting this to false will break the in-game handbook, but might help with some
* startup crashes */
public boolean loadHandbook = true;
/** <b>[Client-only]</b> Whether to allow the Arcane Workbench and lectern search field to lose focus and start unfocused. */
public boolean unfocusedSearchBars = false;
/** <b>[Client-only]</b> The position of the spell HUD. */
public GuiPosition spellHUDPosition = GuiPosition.BOTTOM_LEFT;
public static final String DEFAULT_HUD_SKIN_KEY = "default"; // Defined here so it's not in a client-only class.
/** <b>[Client-only]</b> The string identifier of the skin used for the spell HUD. */
public String spellHUDSkin = DEFAULT_HUD_SKIN_KEY;
/** <b>[Client-only]</b> Whether to show elemental colors on spell books for discovered spells. */
public boolean spellBookColors = true;
/** Set of constants for each of the eight positions that the spell HUD can be in. */
public enum GuiPosition {
@@ -523,9 +590,32 @@ public final class Settings {
setupArtefactsConfig();
setupResistancesConfig();
// Update the constants with new values
updateConstantsFromSettings();
config.save();
}
/** Updates the Constants class with the current settings values */
public void updateConstantsFromSettings(){
electroblob.wizardry.constants.Constants.MANA_PER_SHARD = this.manaPerShard;
electroblob.wizardry.constants.Constants.MANA_PER_CRYSTAL = this.manaPerCrystal;
electroblob.wizardry.constants.Constants.GRAND_CRYSTAL_MANA = this.grandCrystalMana;
electroblob.wizardry.constants.Constants.UPGRADE_STACK_LIMIT = this.upgradeStackLimit;
electroblob.wizardry.constants.Constants.NON_ELEMENTAL_UPGRADE_BONUS = this.nonElementalUpgradeBonus;
electroblob.wizardry.constants.Constants.COOLDOWN_REDUCTION_PER_LEVEL = (float) this.cooldownReductionPerLevel;
electroblob.wizardry.constants.Constants.STORAGE_INCREASE_PER_LEVEL = this.storageIncreasePerLevel;
electroblob.wizardry.constants.Constants.POTENCY_INCREASE_PER_TIER = (float) this.potencyIncreasePerTier;
electroblob.wizardry.constants.Constants.DURATION_INCREASE_PER_LEVEL = (float) this.durationIncreasePerLevel;
electroblob.wizardry.constants.Constants.RANGE_INCREASE_PER_LEVEL = (float) this.rangeIncreasePerLevel;
electroblob.wizardry.constants.Constants.BLAST_RADIUS_INCREASE_PER_LEVEL = (float) this.blastIncreasePerLevel;
electroblob.wizardry.constants.Constants.FROST_SLOWNESS_PER_LEVEL = (float) this.frostSlownessIncreasePerLevel;
electroblob.wizardry.constants.Constants.SIPHON_MANA_PER_LEVEL = this.siphonManaPerLevel;
electroblob.wizardry.constants.Constants.CONDENSER_TICK_INTERVAL = this.condenserTickInterval;
electroblob.wizardry.item.ItemWand.BASE_SPELL_SLOTS = this.baseSpellSlots;
electroblob.wizardry.data.WizardData.MAX_RECENT_SPELLS = this.baseSpellSlots;
}
void checkForRedundantOptions(String categoryName, Collection<String> validKeys){
ConfigCategory category = config.getCategory(categoryName);
@@ -655,6 +745,62 @@ public final class Settings {
preventBindingSameSpellTwiceToWands = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "manaPerShard", 10,
"The amount of mana a crystal shard is worth.", 1, 1000);
property.setLanguageKey("config." + Wizardry.MODID + ".mana_per_shard");
Wizardry.proxy.setToNumberSliderEntry(property);
manaPerShard = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "manaPerCrystal", 100,
"The amount of mana each magic crystal is worth.", 1, 10000);
property.setLanguageKey("config." + Wizardry.MODID + ".mana_per_crystal");
Wizardry.proxy.setToNumberSliderEntry(property);
manaPerCrystal = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "grandCrystalMana", 400,
"The amount of mana a grand magic crystal is worth.", 1, 10000);
property.setLanguageKey("config." + Wizardry.MODID + ".grand_crystal_mana");
Wizardry.proxy.setToNumberSliderEntry(property);
grandCrystalMana = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "upgradeStackLimit", 3,
"The maximum number of one type of wand upgrade which can be applied to a wand.", 1, 10);
property.setLanguageKey("config." + Wizardry.MODID + ".upgrade_stack_limit");
Wizardry.proxy.setToNumberSliderEntry(property);
upgradeStackLimit = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "nonElementalUpgradeBonus", 3,
"The bonus amount of wand upgrades that can be applied to a non-elemental wand.", 0, 10);
property.setLanguageKey("config." + Wizardry.MODID + ".non_elemental_upgrade_bonus");
Wizardry.proxy.setToNumberSliderEntry(property);
nonElementalUpgradeBonus = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "siphonManaPerLevel", 5,
"The amount of mana given for a kill for each level of siphon upgrade.", 0, 100);
property.setLanguageKey("config." + Wizardry.MODID + ".siphon_mana_per_level");
Wizardry.proxy.setToNumberSliderEntry(property);
siphonManaPerLevel = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "condenserTickInterval", 50,
"The number of ticks between each mana increase for wands with the condenser upgrade.", 1, 1000);
property.setLanguageKey("config." + Wizardry.MODID + ".condenser_tick_interval");
Wizardry.proxy.setToNumberSliderEntry(property);
condenserTickInterval = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "baseSpellSlots", 5,
"The number of spell slots a wand has with no attunement upgrades applied.", 1, 5);
property.setLanguageKey("config." + Wizardry.MODID + ".base_spell_slots");
Wizardry.proxy.setToNumberSliderEntry(property);
baseSpellSlots = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "playersMoveEachOther", true,
"Whether to allow players to move other players around using magic.");
property.setLanguageKey("config." + Wizardry.MODID + ".players_move_each_other");
@@ -779,6 +925,122 @@ public final class Settings {
progressionRequirements = property.getIntList();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "tierMaxCharges", new int[]{700, 1000, 1500, 2500},
"Maximum mana each tier can store (novice, apprentice, advanced, master respectively).");
property.setLanguageKey("config." + Wizardry.MODID + ".tier_max_charges");
property.setRequiresWorldRestart(true);
//tierMaxCharges = property.getIntList();
propOrder.add(property.getName());
// Individual tier configuration
property = config.get(DIFFICULTY_CATEGORY, "noviceMaxCharge", 700,
"Maximum mana a novice wand can store.");
property.setLanguageKey("config." + Wizardry.MODID + ".novice_max_charge");
property.setRequiresWorldRestart(true);
noviceMaxCharge = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "apprenticeMaxCharge", 1000,
"Maximum mana an apprentice wand can store.");
property.setLanguageKey("config." + Wizardry.MODID + ".apprentice_max_charge");
property.setRequiresWorldRestart(true);
apprenticeMaxCharge = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "advancedMaxCharge", 1500,
"Maximum mana an advanced wand can store.");
property.setLanguageKey("config." + Wizardry.MODID + ".advanced_max_charge");
property.setRequiresWorldRestart(true);
advancedMaxCharge = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "masterMaxCharge", 2500,
"Maximum mana a master wand can store.");
property.setLanguageKey("config." + Wizardry.MODID + ".master_max_charge");
property.setRequiresWorldRestart(true);
masterMaxCharge = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "noviceUpgradeLimit", 3,
"Maximum number of upgrades a novice wand can have.");
property.setLanguageKey("config." + Wizardry.MODID + ".novice_upgrade_limit");
property.setRequiresWorldRestart(true);
noviceUpgradeLimit = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "apprenticeUpgradeLimit", 5,
"Maximum number of upgrades an apprentice wand can have.");
property.setLanguageKey("config." + Wizardry.MODID + ".apprentice_upgrade_limit");
property.setRequiresWorldRestart(true);
apprenticeUpgradeLimit = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "advancedUpgradeLimit", 7,
"Maximum number of upgrades an advanced wand can have.");
property.setLanguageKey("config." + Wizardry.MODID + ".advanced_upgrade_limit");
property.setRequiresWorldRestart(true);
advancedUpgradeLimit = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "masterUpgradeLimit", 9,
"Maximum number of upgrades a master wand can have.");
property.setLanguageKey("config." + Wizardry.MODID + ".master_upgrade_limit");
property.setRequiresWorldRestart(true);
masterUpgradeLimit = property.getInt();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "condenserAmountMultiplier", 1.0,
"Multiplier for condenser upgrade mana regeneration amount. Higher values make condensers more effective.");
property.setLanguageKey("config." + Wizardry.MODID + ".condenser_amount_multiplier");
property.setRequiresWorldRestart(true);
condenserAmountMultiplier = property.getDouble();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "fleshSpellsCauseSlowness", true,
"Whether flesh spells (DiamondFlesh, IronFlesh, OakFlesh) apply slowness. When disabled, these spells only provide their defensive benefits without movement penalty.");
property.setLanguageKey("config." + Wizardry.MODID + ".flesh_spells_cause_slowness");
Wizardry.proxy.setToNamedBooleanEntry(property);
property.requiresMcRestart();
fleshSpellsCauseSlowness = property.getBoolean();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "diamondFleshArmorBonus", 4.0,
"Armor bonus provided by the DiamondFlesh spell.");
property.setLanguageKey("config." + Wizardry.MODID + ".diamond_flesh_armor_bonus");
property.setRequiresWorldRestart(true);
diamondFleshArmorBonus = property.getDouble();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "diamondFleshArmorToughnessBonus", 3.0,
"Armor toughness bonus provided by the DiamondFlesh spell.");
property.setLanguageKey("config." + Wizardry.MODID + ".diamond_flesh_armor_toughness_bonus");
property.setRequiresWorldRestart(true);
diamondFleshArmorToughnessBonus = property.getDouble();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "ironFleshArmorBonus", 4.0,
"Armor bonus provided by the IronFlesh spell.");
property.setLanguageKey("config." + Wizardry.MODID + ".iron_flesh_armor_bonus");
property.setRequiresWorldRestart(true);
ironFleshArmorBonus = property.getDouble();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "oakFleshArmorBonus", 3.0,
"Armor bonus provided by the OakFlesh spell.");
property.setLanguageKey("config." + Wizardry.MODID + ".oak_flesh_armor_bonus");
property.setRequiresWorldRestart(true);
oakFleshArmorBonus = property.getDouble();
propOrder.add(property.getName());
property = config.get(DIFFICULTY_CATEGORY, "oakFleshHealthBonus", 0.2,
"Health bonus provided by the OakFlesh spell (as a multiplier, e.g., 0.2 = 20% increase).");
property.setLanguageKey("config." + Wizardry.MODID + ".oak_flesh_health_bonus");
property.setRequiresWorldRestart(true);
oakFleshHealthBonus = property.getDouble();
propOrder.add(property.getName());
// These two aren't sliders because using a slider makes it difficult to fine-tune the numbers; the nature of a
// scaling factor means that 0.5 is as big a change as 2.0, so whilst a slider is fine for increasing the
// damage, it doesn't give fine enough control for values less than 1.
@@ -850,6 +1112,13 @@ public final class Settings {
replaceVanillaFallDamage = property.getBoolean();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "spellBookColorsRequireArchivistsEyeglass", false,
"If true, spell book colors are only shown when the player has the charm of spell discovery equipped.");
property.setLanguageKey("config." + Wizardry.MODID + ".spell_book_colors_require_charm");
Wizardry.proxy.setToNamedBooleanEntry(property);
spellBookColorsRequireArchivistsEyeglass = property.getBoolean();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "blindnessTweak", true,
"Whether to tweak the blindness effect to reduce follow distance when used on non-players. This automatically disables itself in favour of Potion Core's implementation if installed.");
property.setLanguageKey("config." + Wizardry.MODID + ".blindness_tweak");
@@ -864,6 +1133,14 @@ public final class Settings {
injectMobDrops = property.getBoolean();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "recentSpellExpiryTime", 1200,
"The time in ticks after which recent spell casts expire and no longer count toward progression penalties. Default is 1200 ticks (1 minute). Lower values make progression penalties shorter-lived, higher values make them last longer.",
60, 72000); // Between 3 seconds and 1 hour
property.setLanguageKey("config." + Wizardry.MODID + ".recent_spell_expiry_time");
Wizardry.proxy.setToNumberSliderEntry(property);
recentSpellExpiryTime = property.getInt();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "mobLootTableWhitelist", new String[0], "Whitelist for loot tables to inject additional mob drops (as specified in loot_tables/entities/mob_additions.json) into. Wizardry makes a best guess as to which loot tables belong to hostile mobs, but this may not always be correct or appropriate; add loot table locations (not entity IDs) to this list to manually include them.");
property.setLanguageKey("config." + Wizardry.MODID + ".mob_loot_table_whitelist");
property.setRequiresMcRestart(true);
@@ -952,32 +1229,60 @@ public final class Settings {
bookshelfSearchRadius = property.getInt();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "currencyItems", new String[]{"gold_ingot 3", "emerald 6"}, "List of registry names of items which wizard trades can use as currency (in the first slot; the second slot is unaffected). Each entry in this list should consist of an item registry name, followed by a single space, then an integer which defines the 'value' of the item. Higher values mean fewer of that currency item are required for a given trade.",
Pattern.compile("[A-Za-z0-9:_]+ [0-9]+"));
property = config.get(TWEAKS_CATEGORY, "currencyItems", new String[]{"gold_ingot 3", "emerald 6"}, "List of registry names of items which wizard trades can use as currency (in the first slot; the second slot is unaffected). Each entry in this list should consist of an item registry name, followed by a single space, then an integer which defines the 'value' of the item. Higher values mean fewer of that currency item are required for a given trade. To specify metadata, use the format 'modid:item:meta value'. For example, 'minecraft:wool:1 5'. If no metadata is given, any metadata will be accepted. NBT tags may optionally be specified in SNBT format immediately after the item/metadata, e.g. 'minecraft:potion{Potion:\"minecraft:strength\"} 3'.",
Pattern.compile(".+ [0-9]+"));
property.setLanguageKey("config." + Wizardry.MODID + ".currency_items");
propOrder.add(property.getName());
currencyItems = new HashMap<>();
currencyItemNbt = new HashMap<>();
for(String string : property.getStringList()){
string = string.toLowerCase(Locale.ROOT).trim();
String[] args = string.split(" ");
if(args.length != 2){
string = string.trim();
// Split on the last space to separate the item spec from the integer value; this correctly
// handles NBT strings that contain spaces inside quoted values.
int lastSpace = string.lastIndexOf(' ');
if(lastSpace < 0){
Wizardry.logger.warn("Invalid entry in currency items: {}", string);
continue; // Ignore invalid entries, the pattern above should ensure this never happens
continue;
}
String itemSpec = string.substring(0, lastSpace).toLowerCase(Locale.ROOT);
String valueStr = string.substring(lastSpace + 1);
// Check for an optional NBT tag (do NOT lowercase the raw NBT string - it is case-sensitive)
int nbtStart = itemSpec.indexOf('{');
NBTTagCompound nbt = null;
if(nbtStart >= 0){
// Re-extract the NBT portion from the original (non-lowercased) string to preserve case
String rawNbt = string.substring(nbtStart, lastSpace);
itemSpec = itemSpec.substring(0, nbtStart);
try{
nbt = JsonToNBT.getTagFromJson(rawNbt);
}catch(NBTException e){
Wizardry.logger.warn("Invalid NBT in currency items '{}': {}", string, e.getMessage());
continue;
}
}
try{
currencyItems.put(parseItemMetaString(args[0]), Integer.parseInt(args[1]));
Pair<ResourceLocation, Short> key = parseItemMetaString(itemSpec);
currencyItems.put(key, Integer.parseInt(valueStr));
if(nbt != null) currencyItemNbt.put(key, nbt);
}catch(NumberFormatException e){
Wizardry.logger.warn("Invalid integer in currency items: {}", args[1]);
Wizardry.logger.warn("Invalid integer in currency items: {}", valueStr);
}
}
property = config.get(TWEAKS_CATEGORY, "cooldown_reduction_per_level", 0.15,
"The fraction by which cooldowns are reduced for each level of cooldown upgrade.",
0.05, Integer.MAX_VALUE); // Sure, I mean you COULD set it to 2^31-1... what could possibly go wrong?
property.setLanguageKey("config." + Wizardry.MODID + ".cast_command_multiplier_limit");
property.setLanguageKey("config." + Wizardry.MODID + ".cooldown_reduction_per_level");
cooldownReductionPerLevel = property.getDouble();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "storage_increase_per_level", 0.15,
"The fraction by which maximum charge is increased for each level of storage upgrade.",
0.05, Integer.MAX_VALUE);
property.setLanguageKey("config." + Wizardry.MODID + ".storage_increase_per_level");
storageIncreasePerLevel = (float) property.getDouble();
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "potency_increase_per_tier", 0.15,
"The fraction by which potency is increased for each tier of matching wand. May cause extreme lag with high values!",
0.05, Integer.MAX_VALUE);
@@ -1112,6 +1417,22 @@ public final class Settings {
shrineFiles = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "shrineRegenerationEnabled", false, "Whether conquered shrines should regenerate after a period of time. When disabled, shrines remain conquered permanently.");
property.setLanguageKey("config." + Wizardry.MODID + ".shrine_regeneration_enabled");
shrineRegenerationEnabled = property.getBoolean();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "shrineRegenerationTime", 20, "Time in minutes for a conquered shrine to regenerate. Minimum 1 minute, maximum 1440 minutes (24 hours).", 1, 1440);
property.setLanguageKey("config." + Wizardry.MODID + ".shrine_regeneration_time");
Wizardry.proxy.setToNumberSliderEntry(property);
shrineRegenerationTime = property.getInt();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "shrineAllowMultipleLoot", false, "Whether players can loot shrines multiple times. If false, each player can only loot each shrine once until it regenerates.");
property.setLanguageKey("config." + Wizardry.MODID + ".shrine_allow_multiple_loot");
shrineAllowMultipleLoot = property.getBoolean();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "libraryDimensions", new int[]{0}, "List of dimension ids in which library ruins will generate. Remove all dimensions to disable library ruins completely.");
property.setLanguageKey("config." + Wizardry.MODID + ".library_dimensions");
property.setRequiresWorldRestart(true);
@@ -1204,12 +1525,26 @@ public final class Settings {
showChargeMeter = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "spellBookColors", true, "Whether to show elemental colors on spell books for discovered spells.");
property.setLanguageKey("config." + Wizardry.MODID + ".spell_book_colors");
property.setRequiresWorldRestart(false);
Wizardry.proxy.setToNamedBooleanEntry(property);
spellBookColors = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "loadHandbook", true, "Whether to initialise the in-game handbook. Setting this to false will brick the in-game handbook, but it might help if you have startup crashes.");
property.setLanguageKey("config." + Wizardry.MODID + ".load_handbook");
property.setRequiresWorldRestart(false);
Wizardry.proxy.setToNamedBooleanEntry(property);
loadHandbook = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "unfocusedSearchBars", false, "Whether to allow the Arcane Workbench and lectern search field to lose focus and start unfocused. If true, the search field won't automatically capture keyboard input.");
property.setLanguageKey("config." + Wizardry.MODID + ".unfocused_search_bars");
property.setRequiresWorldRestart(false);
Wizardry.proxy.setToNamedBooleanEntry(property);
unfocusedSearchBars = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "spellHUDPosition", GuiPosition.BOTTOM_LEFT.name, "The position of the spell HUD.", GuiPosition.names);
property.setLanguageKey("config." + Wizardry.MODID + ".spell_hud_position");
@@ -1371,6 +1706,18 @@ public final class Settings {
}
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToMagic", new String[]{},
"List of names of entities that are immune to magic, in addition to the defaults. Add mod creatures to this list if you want them to be immune to magic damage and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_magic");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
// Converts all strings in the list to lower case, to ignore case sensitivity, and trims them.
for(int i = 0; i < property.getStringList().length; i++){
property.getStringList()[i] = property.getStringList()[i].toLowerCase(Locale.ROOT).trim();
MagicDamage.addEntityImmunity(EntityList.getClass(new ResourceLocation(property.getStringList()[i])),
DamageType.MAGIC);
}
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToWither", new String[]{},
"List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_wither");
@@ -1515,16 +1862,46 @@ public final class Settings {
string = string.toLowerCase(Locale.ROOT).trim();
String[] itemArgs = string.split(":");
String item;
short meta;
try {
meta = Short.parseShort(itemArgs[itemArgs.length-1]);
item = String.join(":", Arrays.copyOfRange(itemArgs, 0, itemArgs.length-1));
}catch(NumberFormatException e){ // If no metadata is specified
meta = OreDictionary.WILDCARD_VALUE;
int lastColon = string.lastIndexOf(':');
if (lastColon > -1 && lastColon < string.length() - 1) { // if there is a colon and it's not the last char
String metaString = string.substring(lastColon + 1);
try {
short parsedMeta = Short.parseShort(metaString);
String itemString = string.substring(0, lastColon);
// If itemString has no colon, it could be 'item:meta' or 'namespace:numeric_path'.
// e.g., 'wool:1' vs 'mod:123'. We want to support 'wool:1' as item 'wool' with meta 1.
// We can't reliably distinguish the two cases without knowing all namespaces, so we have to use
// a heuristic. The original code favoured the 'item:meta' interpretation, let's stick with that.
// The issue was with 'namespace:numeric_path' which this logic now handles.
if (itemString.indexOf(':') == -1) {
item = itemString;
meta = parsedMeta;
} else {
// Disambiguate things like 'mod:123:4' (item 'mod:123' with meta 4) vs 'mod:item:123' (item 'mod:item' with meta 123)
// If the bit before the last colon is a valid resource location, it's probably item:meta
try {
new ResourceLocation(itemString);
item = itemString;
meta = parsedMeta;
} catch (Exception e) {
item = string;
meta = OreDictionary.WILDCARD_VALUE;
}
}
} catch (NumberFormatException e) {
// The part after colon is not a number, so it's part of the name
item = string;
meta = OreDictionary.WILDCARD_VALUE;
}
} else {
item = string;
meta = OreDictionary.WILDCARD_VALUE;
}
return Pair.of(new ResourceLocation(item), meta);
@@ -82,7 +82,7 @@ public class Wizardry {
* 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft
* 1.11.x versions, and so on.
*/
public static final String VERSION = "4.3.14";
public static final String VERSION = "4.3.18";
/**
* Json file used by Forge's built-in <a href="https://mcforge.readthedocs.io/en/1.12.x/gettingstarted/autoupdate/">update checker</a>.
@@ -164,6 +164,9 @@ public class Wizardry {
settings.initConfigExtras();
// Update constants with configured values
settings.updateConstantsFromSettings();
// World generators
// Weight is a misnomer, it's actually the priority (where lower numbers get generated first)
// Literally nothing on typical 'weight' values here, there isn't even an upper limit
@@ -195,6 +198,8 @@ public class Wizardry {
// Client-side stuff (via proxies)
proxy.initGuiBits();
proxy.registerParticles();
proxy.registerItemColorHandlers();
proxy.registerModelProperties();
proxy.registerSoundEventListener();
}
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.inventory.ContainerBookshelf;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.tileentity.TileEntityBookshelf;
@@ -22,6 +23,7 @@ import net.minecraft.init.Items;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
@@ -162,7 +164,37 @@ public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvid
TileEntity tileEntity = world.getTileEntity(pos);
if(tileEntity == null || player.isSneaking()){
if(tileEntity == null){
return false;
}
if(player.isSneaking()){
ItemStack heldItem = player.getHeldItem(hand);
if(heldItem.isEmpty() && tileEntity instanceof TileEntityBookshelf){
if(!world.isRemote){
TileEntityBookshelf bookshelf = (TileEntityBookshelf)tileEntity;
for(int i = 0; i < bookshelf.getSizeInventory(); i++){
ItemStack stack = bookshelf.getStackInSlot(i);
if(!stack.isEmpty()){
player.addItemStackToInventory(stack.copy());
bookshelf.setInventorySlotContents(i, ItemStack.EMPTY);
break;
}
}
}
return true;
}else if(!heldItem.isEmpty() && ContainerBookshelf.isBook(heldItem) && tileEntity instanceof TileEntityBookshelf){
if(!world.isRemote){
TileEntityBookshelf bookshelf = (TileEntityBookshelf)tileEntity;
for(int i = 0; i < bookshelf.getSizeInventory(); i++){
if(bookshelf.getStackInSlot(i).isEmpty()){
bookshelf.setInventorySlotContents(i, heldItem.splitStack(1));
break;
}
}
}
return true;
}
return false;
}
@@ -57,7 +57,7 @@ public class BlockCrystalFlower extends BlockBush {
@SubscribeEvent
public static void onBonemealEvent(BonemealEvent event){
// Grows crystal flowers when bonemeal is used on grass
if(Wizardry.settings.bonemealGrowsCrystalFlowers && event.getBlock().getBlock() == Blocks.GRASS){
if(!event.getWorld().isRemote && Wizardry.settings.bonemealGrowsCrystalFlowers && event.getBlock().getBlock() == Blocks.GRASS){
BlockPos pos = event.getPos().add(event.getWorld().rand.nextInt(8) - event.getWorld().rand.nextInt(8),
event.getWorld().rand.nextInt(4) - event.getWorld().rand.nextInt(4),
@@ -1,5 +1,6 @@
package electroblob.wizardry.block;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
@@ -81,11 +82,25 @@ public class BlockPedestal extends Block implements ITileEntityProvider {
@Override
public float getBlockHardness(IBlockState state, World world, BlockPos pos){
// If shrine regeneration is enabled, make pedestal blocks with shrine cores unbreakable to prevent exploitation
if(!world.isRemote && Wizardry.settings != null && Wizardry.settings.shrineRegenerationEnabled){
TileEntity tileEntity = world.getTileEntity(pos);
if(tileEntity instanceof TileEntityShrineCore){
return -1; // Unbreakable if it has a shrine core
}
}
return state.getValue(NATURAL) ? -1 : super.getBlockHardness(state, world, pos);
}
@Override
public float getExplosionResistance(World world, BlockPos pos, @Nullable Entity exploder, Explosion explosion){
// If shrine regeneration is enabled, make pedestal blocks with shrine cores unbreakable to prevent exploitation
if(!world.isRemote && Wizardry.settings != null && Wizardry.settings.shrineRegenerationEnabled){
TileEntity tileEntity = world.getTileEntity(pos);
if(tileEntity instanceof TileEntityShrineCore){
return 6000000.0F; // Unbreakable if it has a shrine core
}
}
return world.getBlockState(pos).getValue(NATURAL) ? 6000000.0F : super.getExplosionResistance(world, pos, exploder, explosion);
}
@@ -191,7 +191,7 @@ public class BlockStatue extends Block implements ITileEntityProvider {
// Making this an instance method means it works equally well for both types of statue
public boolean convertToStatue(EntityLiving target, @Nullable EntityLivingBase caster, int duration){
if(target.deathTime > 0) return false;
if(target.deathTime > 0 || target.world.isRemote) return false;
BlockPos pos = new BlockPos(target);
World world = target.world;
@@ -1,7 +1,6 @@
package electroblob.wizardry.client;
import electroblob.wizardry.CommonProxy;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.client.animation.ActionAnimation;
@@ -19,6 +18,8 @@ import electroblob.wizardry.client.model.ModelRobeArmour;
import electroblob.wizardry.client.model.ModelSageArmour;
import electroblob.wizardry.client.model.ModelWizardArmour;
import electroblob.wizardry.client.particle.*;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory;
import electroblob.wizardry.client.renderer.RenderSpectralGolem;
import electroblob.wizardry.client.renderer.entity.*;
@@ -38,6 +39,7 @@ import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemScroll;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWand;
@@ -715,6 +717,65 @@ public class ClientProxy extends CommonProxy {
}else Wizardry.logger.warn("Received a PacketConquerShrine, but there was no shrine core at the position sent");
}
@Override
public void handleBombExplosionPacket(PacketBombExplosion.Message message){
net.minecraft.world.World world = Minecraft.getMinecraft().world;
double x = message.x, y = message.y, z = message.z;
float blastMultiplier = message.blastMultiplier;
switch(message.bombType){
case PacketBombExplosion.FIREBOMB:
ParticleBuilder.create(Type.FLASH).pos(x, y, z).scale(5 * blastMultiplier).clr(1, 0.6f, 0).spawn(world);
for(int i = 0; i < 60 * blastMultiplier; i++){
ParticleBuilder.create(Type.MAGIC_FIRE, world.rand, x, y, z, 2 * blastMultiplier, false)
.time(10 + world.rand.nextInt(4)).scale(2 + world.rand.nextFloat()).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC, world.rand, x, y, z, 2 * blastMultiplier, false)
.clr(1.0f, 0.2f + world.rand.nextFloat() * 0.4f, 0.0f).spawn(world);
}
world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, x, y, z, 0, 0, 0);
break;
case PacketBombExplosion.POISON_BOMB:
ParticleBuilder.create(Type.FLASH).pos(x, y, z).scale(5 * blastMultiplier)
.clr(0.2f + world.rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world);
for(int i = 0; i < 60 * blastMultiplier; i++){
ParticleBuilder.create(Type.SPARKLE, world.rand, x, y, z, 2 * blastMultiplier, false).time(35)
.scale(2).clr(0.2f + world.rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC, world.rand, x, y, z, 2 * blastMultiplier, false)
.clr(0.2f + world.rand.nextFloat() * 0.2f, 0.8f, 0.0f).spawn(world);
}
world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, x, y, z, 0, 0, 0);
break;
case PacketBombExplosion.SMOKE_BOMB:
ParticleBuilder.create(Type.FLASH).pos(x, y, z).scale(5 * blastMultiplier).clr(0, 0, 0).spawn(world);
world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, x, y, z, 0, 0, 0);
for(int i = 0; i < 60 * blastMultiplier; i++){
float brightness = world.rand.nextFloat() * 0.1f + 0.1f;
ParticleBuilder.create(Type.CLOUD, world.rand, x, y, z, 2 * blastMultiplier, false)
.clr(brightness, brightness, brightness).time(80 + world.rand.nextInt(12)).shaded(true).spawn(world);
brightness = world.rand.nextFloat() * 0.3f;
ParticleBuilder.create(Type.DARK_MAGIC, world.rand, x, y, z, 2 * blastMultiplier, false)
.clr(brightness, brightness, brightness).spawn(world);
}
break;
case PacketBombExplosion.SPARK_BOMB:
ParticleBuilder.spawnShockParticles(world, x, y, z);
for(int id : message.secondaryTargetIDs){
net.minecraft.entity.Entity target = world.getEntityByID(id);
if(target instanceof net.minecraft.entity.EntityLivingBase){
ParticleBuilder.create(Type.LIGHTNING).pos(x, y, z).target(target).spawn(world);
ParticleBuilder.spawnShockParticles(world, target.posX,
target.posY + target.height / 2, target.posZ);
}
}
break;
}
}
// Rendering
// ===============================================================================================================
@@ -877,4 +938,51 @@ public class ClientProxy extends CommonProxy {
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityImbuementAltar.class, new RenderImbuementAltar());
}
public void registerItemColorHandlers() {
// Spell book overlay textures are now per-element, so no runtime tinting is needed.
}
public void registerModelProperties() {
Item spellBook = WizardryItems.spell_book;
spellBook.addPropertyOverride(new ResourceLocation(Wizardry.MODID, "discovered"), (stack, world, entity) -> {
if (!Wizardry.settings.spellBookColors) return 0.0f;
boolean discovered = shouldDisplayDiscovered(Spell.byMetadata(stack.getMetadata()), stack);
if(discovered && Wizardry.settings.spellBookColorsRequireArchivistsEyeglass){
// Entity can be null so we have to check
if(entity instanceof EntityPlayer){
return ItemArtefact.isArtefactActive((EntityPlayer)entity, WizardryItems.charm_spell_discovery) ? 1.0f : 0.0f;
}
// If there's no entity, there's no charm, so no colour
return 0.0f;
}
return discovered ? 1.0f : 0.0f;
});
// Returns ordinal+1 for each element (1=MAGIC through 8=HEALING), or 0 if not yet discovered.
// Drives per-element bookmark texture selection via model overrides in spell_book.json.
spellBook.addPropertyOverride(new ResourceLocation(Wizardry.MODID, "element"), (stack, world, entity) -> {
if (!Wizardry.settings.spellBookColors) return 0.0f;
Spell spell = Spell.byMetadata(stack.getMetadata());
boolean discovered = shouldDisplayDiscovered(spell, stack);
if(!discovered) return 0.0f;
if(Wizardry.settings.spellBookColorsRequireArchivistsEyeglass){
if(entity instanceof EntityPlayer){
if(!ItemArtefact.isArtefactActive((EntityPlayer)entity, WizardryItems.charm_spell_discovery)) return 0.0f;
} else {
return 0.0f;
}
}
Element element = spell.getElement();
return element == null ? 0.0f : (float)(element.ordinal() + 1);
});
}
}
@@ -1,8 +1,10 @@
package electroblob.wizardry.client;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.renderer.overlay.RenderBlinkEffect;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.*;
import electroblob.wizardry.potion.PotionSlowTime;
import electroblob.wizardry.registry.WizardryItems;
@@ -13,6 +15,7 @@ import electroblob.wizardry.spell.SlowTime;
import electroblob.wizardry.spell.Transience;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
@@ -23,14 +26,14 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.InputUpdateEvent;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.*;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
@@ -185,6 +188,26 @@ public final class WizardryClientEventHandler {
}
}
@SubscribeEvent
public static void onGuiOpenEvent(GuiOpenEvent event){
if(Minecraft.getMinecraft().player != null && event.getGui() instanceof GuiContainer) {
WizardData data = WizardData.get(Minecraft.getMinecraft().player);
if (data != null && data.getVariable(Possession.POSSESSEE_KEY) != null) {
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onItemTooltip(ItemTooltipEvent event) {
if (event.getItemStack().getItem() == WizardryItems.charm_spell_discovery) {
if (Wizardry.settings.spellBookColorsRequireArchivistsEyeglass) {
event.getToolTip().add(TextFormatting.GRAY + I18n.format("item.ebwizardry:charm_spell_discovery.desc.color"));
}
}
}
/**
* Renders an overlay across the entire screen.
* @param resolution The screen resolution
@@ -137,8 +137,8 @@ public class GuiArcaneWorkbench extends GuiContainer {
this.searchField.setEnableBackgroundDrawing(false);
this.searchField.setVisible(true);
this.searchField.setTextColor(16777215);
this.searchField.setCanLoseFocus(false);
this.searchField.setFocused(true);
this.searchField.setCanLoseFocus(Wizardry.settings.unfocusedSearchBars); // false by default
this.searchField.setFocused(!Wizardry.settings.unfocusedSearchBars); // true by default
this.tooltipElements.clear();
this.tooltipElements.add(new TooltipElementItemName(new Style().setColor(TextFormatting.WHITE), LINE_SPACING_WIDE));
@@ -431,6 +431,16 @@ public class GuiArcaneWorkbench extends GuiContainer {
// Controls
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
super.mouseClicked(mouseX, mouseY, mouseButton);
if (this.searchField != null) {
this.searchField.mouseClicked(mouseX, mouseY, mouseButton);
// Set focus depending on whether the click was inside the search field
this.searchField.setFocused(this.searchField.isFocused());
}
}
@Override
protected void actionPerformed(GuiButton button){
@@ -505,12 +515,13 @@ public class GuiArcaneWorkbench extends GuiContainer {
@Override
protected void keyTyped(char typedChar, int keyCode) throws IOException {
if(this.searchNeedsClearing){
this.searchNeedsClearing = false;
if(this.searchNeedsClearing){
this.searchNeedsClearing = false;
this.searchField.setText("");
}
if(this.searchField.textboxKeyTyped(typedChar, keyCode)){
// Allow exiting the GUI by pressing the inventory button. Does not work if bookshelves are present by default
if(this.searchField.getVisible() && this.searchField.textboxKeyTyped(typedChar, keyCode)){
arcaneWorkbenchContainer.setSearchText(searchField.getText().toLowerCase(Locale.ROOT));
}else{
super.keyTyped(typedChar, keyCode);
@@ -1052,4 +1063,4 @@ public class GuiArcaneWorkbench extends GuiContainer {
}
}
}
}
@@ -184,9 +184,8 @@ public class GuiLectern extends GuiSpellInfo implements ISpellSortable {
this.searchField.setEnableBackgroundDrawing(false);
this.searchField.setVisible(true);
this.searchField.setTextColor(16777215);
this.searchField.setCanLoseFocus(false);
this.searchField.setFocused(true);
this.searchField.setCanLoseFocus(Wizardry.settings.unfocusedSearchBars); // false by default
this.searchField.setFocused(!Wizardry.settings.unfocusedSearchBars); // true by default
refreshAvailableSpells(); // Must be done last
}
@@ -269,8 +268,13 @@ public class GuiLectern extends GuiSpellInfo implements ISpellSortable {
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
super.mouseClicked(mouseX, mouseY, mouseButton);
searchNeedsClearing = true;
super.mouseClicked(mouseX, mouseY, mouseButton);
if (this.searchField != null) {
this.searchField.mouseClicked(mouseX, mouseY, mouseButton);
// Set focus depending on whether the click was inside the search field
this.searchField.setFocused(this.searchField.isFocused());
}
searchNeedsClearing = true;
}
@Override
@@ -60,10 +60,12 @@ public class RenderSixthSense {
Minecraft mc = Minecraft.getMinecraft();
RenderManager renderManager = event.getRenderer().getRenderManager();
float effectRadius = Spells.sixth_sense.getProperty(Spell.EFFECT_RADIUS).floatValue();
float distance = event.getEntity().getDistance(mc.player);
if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand)
&& event.getEntity() != mc.player && mc.player.getActivePotionEffect(WizardryPotions.sixth_sense) != null
&& event.getEntity().getDistance(mc.player) < Spells.sixth_sense.getProperty(Spell.EFFECT_RADIUS).floatValue()
&& distance < effectRadius
* (1 + mc.player.getActivePotionEffect(WizardryPotions.sixth_sense).getAmplifier() * Constants.RANGE_INCREASE_PER_LEVEL)){
Tessellator tessellator = Tessellator.getInstance();
@@ -87,7 +89,13 @@ public class RenderSixthSense {
GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F);
GlStateManager.color(1, 1, 1, 1);
//Decreases the opacity of the marker after 80% of the effect distance
float alpha = 1f;
float f = 5 * (1f - distance / effectRadius);
if (f <= 1) {
alpha = f;
}
GlStateManager.color(1, 1, 1, alpha);
ResourceLocation texture = PASSIVE_MOB_MARKER_TEXTURE;
@@ -8,40 +8,40 @@ public final class Constants {
/** The amount of mana a crystal shard is worth */
// 100 doesn't divide nicely by 9 so we're calling this 10. I guess you lose a little bit by smashing a crystal.
public static final int MANA_PER_SHARD = 10;
public static int MANA_PER_SHARD;
/** The amount of mana each magic crystal is worth */
public static final int MANA_PER_CRYSTAL = 100;
public static int MANA_PER_CRYSTAL;
/** The amount of mana a grand magic crystal is worth */
public static final int GRAND_CRYSTAL_MANA = 400;
public static int GRAND_CRYSTAL_MANA;
/** The maximum number of one type of wand upgrade which can be applied to a wand. */
public static final int UPGRADE_STACK_LIMIT = 3;
public static int UPGRADE_STACK_LIMIT;
/** The bonus amount of wand upgrades that can be applied to a non-elemental wand. */
public static final int NON_ELEMENTAL_UPGRADE_BONUS = 3;
public static int NON_ELEMENTAL_UPGRADE_BONUS;
/** The fraction by which cooldowns are reduced for each level of cooldown upgrade. */
public static float COOLDOWN_REDUCTION_PER_LEVEL = 0.15f;
public static float COOLDOWN_REDUCTION_PER_LEVEL;
/** The fraction by which maximum charge is increased for each level of storage upgrade. */
public static final float STORAGE_INCREASE_PER_LEVEL = 0.15f;
public static float STORAGE_INCREASE_PER_LEVEL;
/** The fraction by which potency is increased for each tier of matching wand. */
public static float POTENCY_INCREASE_PER_TIER = 0.15f;
public static float POTENCY_INCREASE_PER_TIER;
/** The fraction by which spell duration is increased for each level of duration upgrade. */
public static float DURATION_INCREASE_PER_LEVEL = 0.25f;
public static float DURATION_INCREASE_PER_LEVEL;
/** The fraction by which spell range is increased for each level of range upgrade. */
public static float RANGE_INCREASE_PER_LEVEL = 0.25f;
public static float RANGE_INCREASE_PER_LEVEL;
/** The fraction by which spell blast radius is increased for each level of range upgrade. */
public static float BLAST_RADIUS_INCREASE_PER_LEVEL = 0.25f;
public static float BLAST_RADIUS_INCREASE_PER_LEVEL;
/** The fraction by which movement speed is reduced per level of frost effect. */
public static final double FROST_SLOWNESS_PER_LEVEL = 0.5;
public static double FROST_SLOWNESS_PER_LEVEL;
/** The fraction by which movement speed is reduced per level of decay effect. */
public static final double DECAY_SLOWNESS_PER_LEVEL = 0.2;
/** The fraction by which dig speed is reduced per level of frostbite effect. */
public static final float FROST_FATIGUE_PER_LEVEL = 0.45f;
/** The number of ticks between each mana increase for wands with the condenser upgrade. */
public static final int CONDENSER_TICK_INTERVAL = 50;
public static int CONDENSER_TICK_INTERVAL;
/**
* The amount of mana given for a kill for each level of siphon upgrade. A random amount from 0 to this number - 1
* is also added. See {@link WizardryEventHandler#onLivingDeathEvent} for more details.
*/
public static final int SIPHON_MANA_PER_LEVEL = 5;
public static int SIPHON_MANA_PER_LEVEL;
/**
* The number of ticks between the spawning of patches of decay when an entity has the decay effect. Note that decay
* won't spawn again if something is already standing in it.
@@ -51,11 +51,19 @@ public final class Constants {
// making this as an update to the existing values to not break addons directly relying on the fields
static {
POTENCY_INCREASE_PER_TIER = (float) Wizardry.settings.potencyIncreasePerTier;
MANA_PER_SHARD = Wizardry.settings.manaPerShard;
MANA_PER_CRYSTAL = Wizardry.settings.manaPerCrystal;
GRAND_CRYSTAL_MANA = Wizardry.settings.grandCrystalMana;
UPGRADE_STACK_LIMIT = Wizardry.settings.upgradeStackLimit;
NON_ELEMENTAL_UPGRADE_BONUS = Wizardry.settings.nonElementalUpgradeBonus;
COOLDOWN_REDUCTION_PER_LEVEL = (float) Wizardry.settings.cooldownReductionPerLevel;
STORAGE_INCREASE_PER_LEVEL = Wizardry.settings.storageIncreasePerLevel;
POTENCY_INCREASE_PER_TIER = (float) Wizardry.settings.potencyIncreasePerTier;
DURATION_INCREASE_PER_LEVEL = (float) Wizardry.settings.durationIncreasePerLevel;
RANGE_INCREASE_PER_LEVEL = (float) Wizardry.settings.rangeIncreasePerLevel;
BLAST_RADIUS_INCREASE_PER_LEVEL = (float) Wizardry.settings.blastIncreasePerLevel;
RANGE_INCREASE_PER_LEVEL = (float) Wizardry.settings.frostSlownessIncreasePerLevel;
FROST_SLOWNESS_PER_LEVEL = (float) Wizardry.settings.frostSlownessIncreasePerLevel;
SIPHON_MANA_PER_LEVEL = Wizardry.settings.siphonManaPerLevel;
CONDENSER_TICK_INTERVAL = Wizardry.settings.condenserTickInterval;
}
}
@@ -10,10 +10,10 @@ import java.util.Random;
public enum Tier {
NOVICE(700, 3, 12, new Style().setColor(TextFormatting.WHITE), "novice"),
APPRENTICE(1000, 5, 5, new Style().setColor(TextFormatting.AQUA), "apprentice"),
ADVANCED(1500, 7, 2, new Style().setColor(TextFormatting.DARK_BLUE), "advanced"),
MASTER(2500, 9, 1, new Style().setColor(TextFormatting.DARK_PURPLE), "master");
NOVICE(Wizardry.settings.noviceMaxCharge, Wizardry.settings.noviceUpgradeLimit, 12, new Style().setColor(TextFormatting.WHITE), "novice"),
APPRENTICE(Wizardry.settings.apprenticeMaxCharge, Wizardry.settings.apprenticeUpgradeLimit, 5, new Style().setColor(TextFormatting.AQUA), "apprentice"),
ADVANCED(Wizardry.settings.advancedMaxCharge, Wizardry.settings.advancedUpgradeLimit, 2, new Style().setColor(TextFormatting.DARK_BLUE), "advanced"),
MASTER(Wizardry.settings.masterMaxCharge, Wizardry.settings.masterUpgradeLimit, 1, new Style().setColor(TextFormatting.DARK_PURPLE), "master");
/** Maximum mana a wand of this tier can store. */
public final int maxCharge;
@@ -7,7 +7,6 @@ import electroblob.wizardry.enchantment.Imbuement;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketPlayerSync;
import electroblob.wizardry.packet.WizardryPacketHandler;
@@ -47,6 +46,7 @@ import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.stream.Collectors;
import java.util.AbstractMap.SimpleEntry;
/**
* Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave minimum
@@ -73,7 +73,7 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
private static final Set<IStoredVariable> storedVariables = new HashSet<>();
/** The maximum number of recent spells to track. */
public static final int MAX_RECENT_SPELLS = ItemWand.BASE_SPELL_SLOTS;
public static int MAX_RECENT_SPELLS;
private static final int IMBUEMENT_UPDATE_INTERVAL = 20;
@@ -126,7 +126,7 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
* be modified via {@link WizardData#setVariable(IVariable, Object)}, which (as a method) is able to enforce it. */
private final Map<IVariable, Object> spellData;
private Queue<Spell> recentSpells;
private Queue<SimpleEntry<Spell, Long>> recentSpells;
// This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry.
// Leaving this for now because merging it into the spell data system will be more tricky
@@ -294,15 +294,19 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
* @param spell The spell to be tracked.
*/
public void trackRecentSpell(Spell spell){
this.recentSpells.add(spell);
this.recentSpells.add(new SimpleEntry<>(spell, player.world.getTotalWorldTime()));
}
/**
* Returns the number of times the given spell is tracked in this player's recently-cast spells.
* Only counts spells cast within the configured expiry time.
* @param spell The spell to count casts for.
*/
public int countRecentCasts(Spell spell){
return (int)this.recentSpells.stream().filter(s -> s == spell).count(); // We know this can't be more than 5
long currentTime = player.world.getTotalWorldTime();
return (int)this.recentSpells.stream()
.filter(entry -> entry.getKey() == spell && (currentTime - entry.getValue()) < Wizardry.settings.recentSpellExpiryTime)
.count(); // We know this can't be more than 5
}
// Imbuements
@@ -351,20 +355,20 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
// For each item in the player's inventory
for(ItemStack stack : player.inventory.mainInventory){
updateImbutedItem(stack, activeImbuements);
updateImbuedItem(stack, activeImbuements);
}
for(ItemStack stack : player.inventory.armorInventory){
updateImbutedItem(stack, activeImbuements);
updateImbuedItem(stack, activeImbuements);
}
for(ItemStack stack : player.inventory.offHandInventory){
updateImbutedItem(stack, activeImbuements);
updateImbuedItem(stack, activeImbuements);
}
// Removes all imbuements from the map that are no longer active
this.imbuementDurations.keySet().retainAll(activeImbuements);
}
private void updateImbutedItem(ItemStack stack, Set<Imbuement> activeImbuements){
private void updateImbuedItem(ItemStack stack, Set<Imbuement> activeImbuements){
if(stack.isItemEnchanted()){
@@ -461,7 +465,7 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
if(this.castCommandSpell != null && this.castCommandSpell.isContinuous){
if(castCommandTick >= castCommandDuration){
if(castCommandTick > castCommandDuration){
this.stopCastingContinuousSpell();
return;
}
@@ -513,6 +517,12 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
if(player.ticksExisted % IMBUEMENT_UPDATE_INTERVAL == 0) updateImbuedItems();
updateContinuousSpellCasting();
// Clean up expired recent spells every 60 ticks (1 second)
if(player.ticksExisted % 60 == 0) {
long currentTime = player.world.getTotalWorldTime();
this.recentSpells.removeIf(entry -> (currentTime - entry.getValue()) >= Wizardry.settings.recentSpellExpiryTime);
}
this.spellData.forEach((k, v) -> this.spellData.put(k, k.update(player, v)));
this.spellData.keySet().removeIf(k -> k.canPurge(player, this.spellData.get(k)));
}
@@ -579,7 +589,15 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
properties.setInteger("maxTierReached", maxTierReached.ordinal());
NBTExtras.storeTagSafely(properties, "recentSpells", NBTExtras.listToNBT(recentSpells, s -> new NBTTagInt(s.metadata())));
// Serialize recent spells with timestamps as compound tags
NBTTagList recentSpellsList = new NBTTagList();
for(SimpleEntry<Spell, Long> entry : recentSpells) {
NBTTagCompound spellTag = new NBTTagCompound();
spellTag.setInteger("spellId", entry.getKey().metadata());
spellTag.setLong("timestamp", entry.getValue());
recentSpellsList.appendTag(spellTag);
}
NBTExtras.storeTagSafely(properties, "recentSpells", recentSpellsList);
storedVariables.forEach(k -> k.write(properties, this.spellData.get(k)));
@@ -606,8 +624,14 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
// Probably won't be null but we may as well just reinitialise it instead of clearing it
this.recentSpells = EvictingQueue.create(MAX_RECENT_SPELLS);
this.recentSpells.addAll(NBTExtras.NBTToList(nbt.getTagList("recentSpells", NBT.TAG_INT),
(NBTTagInt tag) -> Spell.byMetadata(tag.getInt())));
// Deserialize recent spells with timestamps
NBTTagList recentSpellsList = nbt.getTagList("recentSpells", NBT.TAG_COMPOUND);
for(int i = 0; i < recentSpellsList.tagCount(); i++) {
NBTTagCompound spellTag = recentSpellsList.getCompoundTagAt(i);
Spell spell = Spell.byMetadata(spellTag.getInteger("spellId"));
long timestamp = spellTag.getLong("timestamp");
this.recentSpells.add(new SimpleEntry<>(spell, timestamp));
}
try{
storedVariables.forEach(k -> this.spellData.put(k, k.read(nbt)));
@@ -146,23 +146,28 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit
if(EntityUtils.isLiving(target)) nudgeVelocity = 0.25;
Vec3d extraVelocity = targetRelativePos.normalize().scale(nudgeVelocity);
// ...make it bounce off!
target.motionX = target.motionX * -BOUNCINESS + extraVelocity.x;
target.motionY = target.motionY * -BOUNCINESS + extraVelocity.y;
target.motionZ = target.motionZ * -BOUNCINESS + extraVelocity.z;
// Prevents the forcefield bouncing things into the floor
if(target.onGround && target.motionY < 0) target.motionY = 0.1;
// How far the target needs to move towards the centre (negative means away from the centre)
double distanceTowardsCentre = -(targetRelativePos.length() - radius) - (radius - nextTickDistance);
Vec3d targetNewPos = target.getPositionVector().add(targetRelativePos.normalize().scale(distanceTowardsCentre));
target.setPosition(targetNewPos.x, targetNewPos.y, targetNewPos.z);
world.playSound(target.posX, target.posY, target.posZ, WizardrySounds.ENTITY_FORCEFIELD_DEFLECT,
WizardrySounds.SPELLS, 0.3f, 1.3f, false);
//Moved up the check by "19" so that way allied players aren't being moved out of the forcefield because
//allied players aren't synced to client like minions. Minions are only synced because it's saved to their
//entity data which is synced in their serialize/deserialize methods
if(!world.isRemote){
// ...make it bounce off!
target.motionX = target.motionX * -BOUNCINESS + extraVelocity.x;
target.motionY = target.motionY * -BOUNCINESS + extraVelocity.y;
target.motionZ = target.motionZ * -BOUNCINESS + extraVelocity.z;
// Prevents the forcefield bouncing things into the floor
if(target.onGround && target.motionY < 0) target.motionY = 0.1;
// How far the target needs to move towards the centre (negative means away from the centre)
double distanceTowardsCentre = -(targetRelativePos.length() - radius) - (radius - nextTickDistance);
Vec3d targetNewPos = target.getPositionVector().add(targetRelativePos.normalize().scale(distanceTowardsCentre));
target.setPosition(targetNewPos.x, targetNewPos.y, targetNewPos.z);
world.playSound(target.posX, target.posY, target.posZ, WizardrySounds.ENTITY_FORCEFIELD_DEFLECT,
WizardrySounds.SPELLS, 0.3f, 1.3f, false);
// Player motion is handled on that player's client so needs packets
if(target instanceof EntityPlayerMP){
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
@@ -174,6 +179,11 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit
}else{
//This is a super lazy way to make sure the visual isn't playing for players and probably won't display the visual
//for players if their velocity towards the forcefield is high. But it prevents the visual being spammed when ally player
//is in forcefield so it's fine #19
if(target instanceof EntityPlayer && target.getPositionVector().distanceTo(this.getPositionVector()) < this.getRadius()) return;
Vec3d relativeImpactPos = targetRelativePos.normalize().scale(radius);
float yaw = (float)Math.atan2(relativeImpactPos.x, -relativeImpactPos.z);
@@ -617,7 +617,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// ((tier.ordinal() + 1) * 16 + rand.nextInt(6)) gives a 'value' for the item being bought
// This is then divided by the value of the currency item to give a price
// The absolute maximum stack size that can result from this calculation (with value = 1) is 64.
return new ItemStack(item, MathHelper.clamp((8 + tier.ordinal() * 16 + rand.nextInt(9)) / value, 1, 64), meta);
ItemStack result = new ItemStack(item, MathHelper.clamp((8 + tier.ordinal() * 16 + rand.nextInt(9)) / value, 1, 64), meta);
NBTTagCompound nbt = Wizardry.settings.currencyItemNbt.get(itemName);
if(nbt != null) result.setTagCompound(nbt.copy());
return result;
}
private ItemStack getRandomItemOfTier(Tier tier){
@@ -1,18 +1,18 @@
package electroblob.wizardry.entity.projectile;
import electroblob.wizardry.packet.PacketBombExplosion;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.EntityUtils;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import java.util.List;
@@ -29,41 +29,28 @@ public class EntityFirebomb extends EntityBomb {
@Override
protected void onImpact(RayTraceResult rayTrace){
Entity entityHit = rayTrace.entityHit;
if(entityHit != null){
// This is if the firebomb gets a direct hit
float damage = Spells.firebomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
entityHit.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(),
damage);
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit))
entityHit.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue());
}
// Particle effect
if(world.isRemote){
ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector()).scale(5 * blastMultiplier).clr(1, 0.6f, 0)
.spawn(world);
for(int i = 0; i < 60 * blastMultiplier; i++){
ParticleBuilder.create(Type.MAGIC_FIRE, rand, posX, posY, posZ, 2*blastMultiplier, false)
.time(10 + rand.nextInt(4)).scale(2 + rand.nextFloat()).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false)
.clr(1.0f, 0.2f + rand.nextFloat() * 0.4f, 0.0f).spawn(world);
}
this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
}
if(!this.world.isRemote){
Entity entityHit = rayTrace.entityHit;
if(entityHit != null){
// This is if the firebomb gets a direct hit
float damage = Spells.firebomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
entityHit.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(),
damage);
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit))
entityHit.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue());
}
// Notify clients to play the explosion effect
WizardryPacketHandler.net.sendToAllAround(
new PacketBombExplosion.Message(PacketBombExplosion.FIREBOMB, posX, posY, posZ, blastMultiplier),
new NetworkRegistry.TargetPoint(world.provider.getDimension(), posX, posY, posZ, 64));
this.playSound(WizardrySounds.ENTITY_FIREBOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
this.playSound(WizardrySounds.ENTITY_FIREBOMB_FIRE, 1, 1);
@@ -82,9 +69,9 @@ public class EntityFirebomb extends EntityBomb {
target.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue());
}
}
this.setDead();
}
this.setDead();
}
}
@@ -1,20 +1,20 @@
package electroblob.wizardry.entity.projectile;
import electroblob.wizardry.packet.PacketBombExplosion;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.EntityUtils;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.MobEffects;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import java.util.List;
@@ -31,44 +31,30 @@ public class EntityPoisonBomb extends EntityBomb {
@Override
protected void onImpact(RayTraceResult rayTrace){
Entity entityHit = rayTrace.entityHit;
if(entityHit != null){
// This is if the poison bomb gets a direct hit
float damage = Spells.poison_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
entityHit.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON).setProjectile(),
damage);
if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.POISON, entityHit))
((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(MobEffects.POISON,
Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(),
Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue()));
}
// Particle effect
if(world.isRemote){
ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector()).scale(5 * blastMultiplier)
.clr(0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world);
for(int i = 0; i < 60 * blastMultiplier; i++){
ParticleBuilder.create(Type.SPARKLE, rand, posX, posY, posZ, 2*blastMultiplier, false).time(35)
.scale(2).clr(0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false)
.clr(0.2f + rand.nextFloat() * 0.2f, 0.8f, 0.0f).spawn(world);
}
// Spawning this after the other particles fixes the rendering colour bug. It's a bit of a cheat, but it
// works pretty well.
this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
}
if(!this.world.isRemote){
Entity entityHit = rayTrace.entityHit;
if(entityHit != null){
// This is if the poison bomb gets a direct hit
float damage = Spells.poison_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
entityHit.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON).setProjectile(),
damage);
if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.POISON, entityHit))
((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(MobEffects.POISON,
Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(),
Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue()));
}
// Notify clients to play the explosion effect
WizardryPacketHandler.net.sendToAllAround(
new PacketBombExplosion.Message(PacketBombExplosion.POISON_BOMB, posX, posY, posZ, blastMultiplier),
new NetworkRegistry.TargetPoint(world.provider.getDimension(), posX, posY, posZ, 64));
this.playSound(WizardrySounds.ENTITY_POISON_BOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
this.playSound(WizardrySounds.ENTITY_POISON_BOMB_POISON, 1.2F, 1.0f);
@@ -88,8 +74,8 @@ public class EntityPoisonBomb extends EntityBomb {
Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_STRENGTH).intValue()));
}
}
this.setDead();
}
this.setDead();
}
}
@@ -1,17 +1,17 @@
package electroblob.wizardry.entity.projectile;
import electroblob.wizardry.packet.PacketBombExplosion;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.EntityUtils;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.MobEffects;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import java.util.List;
@@ -29,27 +29,13 @@ public class EntitySmokeBomb extends EntityBomb {
@Override
protected void onImpact(RayTraceResult rayTrace){
// Particle effect
if(world.isRemote){
ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector()).scale(5 * blastMultiplier).clr(0, 0, 0).spawn(world);
this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
for(int i = 0; i < 60 * blastMultiplier; i++){
float brightness = rand.nextFloat() * 0.1f + 0.1f;
ParticleBuilder.create(Type.CLOUD, rand, posX, posY, posZ, 2*blastMultiplier, false)
.clr(brightness, brightness, brightness).time(80 + this.rand.nextInt(12)).shaded(true).spawn(world);
brightness = rand.nextFloat() * 0.3f;
ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false)
.clr(brightness, brightness, brightness).spawn(world);
}
}
if(!this.world.isRemote){
// Notify clients to play the explosion effect
WizardryPacketHandler.net.sendToAllAround(
new PacketBombExplosion.Message(PacketBombExplosion.SMOKE_BOMB, posX, posY, posZ, blastMultiplier),
new NetworkRegistry.TargetPoint(world.provider.getDimension(), posX, posY, posZ, 64));
this.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
this.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_SMOKE, 1.2F, 1.0f);
@@ -65,8 +51,8 @@ public class EntitySmokeBomb extends EntityBomb {
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
}
}
this.setDead();
}
this.setDead();
}
}
@@ -1,5 +1,7 @@
package electroblob.wizardry.entity.projectile;
import electroblob.wizardry.packet.PacketBombExplosion;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
@@ -13,7 +15,9 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import java.util.ArrayList;
import java.util.List;
public class EntitySparkBomb extends EntityBomb {
@@ -31,47 +35,37 @@ public class EntitySparkBomb extends EntityBomb {
@Override
protected void onImpact(RayTraceResult rayTrace){
this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT_BLOCK, 0.5f, 0.5f);
Entity entityHit = rayTrace.entityHit;
if(!this.world.isRemote){
if(entityHit != null){
// This is if the spark bomb gets a direct hit
float damage = Spells.spark_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
Entity entityHit = rayTrace.entityHit;
this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
if(entityHit != null){
// This is if the spark bomb gets a direct hit
float damage = Spells.spark_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
entityHit.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(),
damage);
this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
}
entityHit.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(),
damage);
}
// Particle effect
if(world.isRemote){
ParticleBuilder.spawnShockParticles(world, posX, posY + height/2, posZ);
}
double seekerRange = Spells.spark_bomb.getProperty(Spell.EFFECT_RADIUS).doubleValue() * blastMultiplier;
double seekerRange = Spells.spark_bomb.getProperty(Spell.EFFECT_RADIUS).doubleValue() * blastMultiplier;
List<EntityLivingBase> targets = EntityUtils.getLivingWithinRadius(seekerRange, this.posX, this.posY,
this.posZ, this.world);
List<EntityLivingBase> targets = EntityUtils.getLivingWithinRadius(seekerRange, this.posX, this.posY,
this.posZ, this.world);
List<Integer> secondaryTargetIDs = new ArrayList<>();
for(int i = 0; i < Math.min(targets.size(), Spells.spark_bomb.getProperty(SECONDARY_MAX_TARGETS).intValue()); i++){
for(int i = 0; i < Math.min(targets.size(), Spells.spark_bomb.getProperty(SECONDARY_MAX_TARGETS).intValue()); i++){
boolean flag = targets.get(i) != entityHit && targets.get(i) != this.getThrower()
&& !(targets.get(i) instanceof EntityPlayer
&& ((EntityPlayer)targets.get(i)).isCreative());
boolean flag = targets.get(i) != entityHit && targets.get(i) != this.getThrower()
&& !(targets.get(i) instanceof EntityPlayer
&& ((EntityPlayer)targets.get(i)).isCreative());
// Detects (client side) if target is the thrower, to stop particles being spawned around them.
//if(flag && world.isRemote && targets.get(i).getEntityId() == this.playerID) flag = false;
if(flag){
EntityLivingBase target = targets.get(i);
if(!this.world.isRemote){
if(flag){
EntityLivingBase target = targets.get(i);
target.playSound(WizardrySounds.ENTITY_SPARK_BOMB_CHAIN, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
@@ -79,11 +73,18 @@ public class EntitySparkBomb extends EntityBomb {
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK),
Spells.spark_bomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier);
}else{
ParticleBuilder.create(Type.LIGHTNING).pos(this.getPositionVector()).target(target).spawn(world);
ParticleBuilder.spawnShockParticles(world, target.posX, target.posY + target.height/2, target.posZ);
secondaryTargetIDs.add(target.getEntityId());
}
}
// Notify clients to play the explosion effect, including lightning arcs to secondary targets
int[] idArray = secondaryTargetIDs.stream().mapToInt(Integer::intValue).toArray();
WizardryPacketHandler.net.sendToAllAround(
new PacketBombExplosion.Message(PacketBombExplosion.SPARK_BOMB, posX, posY + height / 2, posZ,
blastMultiplier, idArray),
new NetworkRegistry.TargetPoint(world.provider.getDimension(), posX, posY, posZ, 64));
this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT_BLOCK, 0.5f, 0.5f);
}
this.setDead();
@@ -1,13 +1,20 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -25,7 +32,7 @@ public class ItemArcaneTome extends Item {
@Override
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> list){
if(tab == WizardryTabs.WIZARDRY){ // Don't use isInCreativeTab here.
if(tab == WizardryTabs.WIZARDRY || tab == CreativeTabs.SEARCH){ // Don't use isInCreativeTab here.
for(int i = 1; i < Tier.values().length; i++){
list.add(new ItemStack(this, 1, i));
}
@@ -51,6 +58,20 @@ public class ItemArcaneTome extends Item {
return EnumRarity.COMMON;
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
@SideOnly(Side.CLIENT)
@Override
public void addInformation(ItemStack stack, World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag showAdvanced){
@@ -1,6 +1,7 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.Spells;
@@ -8,12 +9,18 @@ import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellProperties;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
public class ItemBlankScroll extends Item implements IWorkbenchItem {
@@ -67,4 +74,18 @@ public class ItemBlankScroll extends Item implements IWorkbenchItem {
return false;
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
}
@@ -34,7 +34,7 @@ public class ItemCrystal extends Item implements IMultiTexturedItem {
@Override
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items){
if(tab == WizardryTabs.WIZARDRY){
if(tab == WizardryTabs.WIZARDRY || tab == CreativeTabs.SEARCH){
for(Element element : Element.values()){
items.add(new ItemStack(this, 1, element.ordinal()));
}
@@ -1,19 +1,23 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.InventoryUtils;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
@@ -47,6 +51,20 @@ public class ItemIdentificationScroll extends Item {
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc");
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand){
@@ -1,6 +1,7 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
@@ -9,6 +10,7 @@ import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -17,8 +19,10 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
@@ -122,6 +126,20 @@ public class ItemScroll extends Item implements ISpellCastingItem, IWorkbenchIte
return CASTING_TIME;
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand){
@@ -160,12 +178,21 @@ public class ItemScroll extends Item implements ISpellCastingItem, IWorkbenchIte
Spell spell = Spell.byMetadata(stack.getItemDamage());
// By default, scrolls have no modifiers - but with the event system, they could be added.
SpellModifiers modifiers = new SpellModifiers();
SpellModifiers modifiers;
if(WizardData.get(player) != null){
modifiers = WizardData.get(player).itemCastingModifiers;
}else{
modifiers = new SpellModifiers();
}
int castingTick = stack.getMaxItemUseDuration() - count;
// Continuous spells (these must check if they can be cast each tick since the mana changes)
// In theory the spell is always continuous here but just in case it isn't...
if(spell.isContinuous && canCast(stack, spell, player, player.getActiveHand(), castingTick, modifiers)){
// Do not check canCast() on tick 0 as it is already done in onItemRightClick() and would duplicate modifiers
if(spell.isContinuous && (castingTick == 0 || canCast(stack, spell, player, player.getActiveHand(), castingTick, modifiers))){
cast(stack, spell, player, player.getActiveHand(), castingTick, modifiers);
}else{
// Scrolls normally work on the max use duration so this isn't ever reached by wizardry, but if the
@@ -29,7 +29,7 @@ public class ItemSpectralDust extends Item implements IMultiTexturedItem {
@Override
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items){
if(tab == WizardryTabs.WIZARDRY){
if(tab == WizardryTabs.WIZARDRY || tab == CreativeTabs.SEARCH){
for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){
items.add(new ItemStack(this, 1, element.ordinal()));
}
@@ -1,6 +1,7 @@
package electroblob.wizardry.item;
import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.constants.Tier;
@@ -14,9 +15,12 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.world.World;
@@ -58,6 +62,21 @@ public class ItemSpellBook extends Item {
}
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
// Minecraft skips onBlockActivated when sneaking with a non-empty hand, so we handle it here
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand){
ItemStack stack = player.getHeldItem(hand);
@@ -70,7 +70,7 @@ import java.util.Random;
public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, IManaStoringItem {
/** The number of spell slots a wand has with no attunement upgrades applied. */
public static final int BASE_SPELL_SLOTS = 5;
public static int BASE_SPELL_SLOTS;
/** The number of ticks between each time a continuous spell is added to the player's recently-cast spells. */
private static final int CONTINUOUS_TRACKING_INTERVAL = 20;
@@ -241,7 +241,9 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem,
// Decrements wand damage (increases mana) every 1.5 seconds if it has a condenser upgrade
if(!world.isRemote && !this.isManaFull(stack) && world.getTotalWorldTime() % Constants.CONDENSER_TICK_INTERVAL == 0){
// If the upgrade level is 0, this does nothing anyway.
this.rechargeMana(stack, WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade));
int baseAmount = WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade);
int amount = (int)(baseAmount * Wizardry.settings.condenserAmountMultiplier);
this.rechargeMana(stack, amount);
}
}
@@ -735,7 +737,7 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem,
Item specialUpgrade = upgrade.getItem();
int maxUpgrades = this.tier.upgradeLimit;
if(this.element == Element.MAGIC) maxUpgrades += Constants.NON_ELEMENTAL_UPGRADE_BONUS;
if(this.element == null) maxUpgrades += Constants.NON_ELEMENTAL_UPGRADE_BONUS;
if(WandHelper.getTotalUpgrades(wand) < maxUpgrades
&& WandHelper.getUpgradeLevel(wand, specialUpgrade) < Constants.UPGRADE_STACK_LIMIT){
@@ -1,10 +1,17 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -24,6 +31,20 @@ public class ItemWandUpgrade extends Item {
return EnumRarity.UNCOMMON;
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flag) {
@@ -2,13 +2,17 @@ package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.util.text.TextFormatting;
@@ -34,6 +38,20 @@ public class ItemWizardHandbook extends Item {
new Style().setColor(TextFormatting.GRAY), AUTHOR));
}
@Override
public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand,
EnumFacing facing, float hitX, float hitY, float hitZ){
if(player.isSneaking()){
IBlockState state = world.getBlockState(pos);
if(state.getBlock() instanceof BlockBookshelf){
if(state.getBlock().onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ)){
return EnumActionResult.SUCCESS;
}
}
}
return EnumActionResult.PASS;
}
@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand){
ItemStack stack = player.getHeldItem(hand);
@@ -78,7 +78,7 @@ public abstract class Forfeit {
public Forfeit(ResourceLocation name){
this.name = name;
this.sound = WizardrySounds.createSound("forfeit." + name.getPath());
this.sound = WizardrySounds.createSound(name.getNamespace(), "forfeit." + name.getPath());
}
public abstract void apply(World world, EntityPlayer player);
@@ -0,0 +1,79 @@
package electroblob.wizardry.packet;
import electroblob.wizardry.Wizardry;
import io.netty.buffer.ByteBuf;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
/**
* <b>[Server -> Client]</b> This packet is sent when a bomb entity impacts something. It is used to guarantee that the
* client always sees the explosion effect, even in singleplayer where the server may remove the entity before the
* client's own simulation can call {@code onImpact()}.
*/
public class PacketBombExplosion implements IMessageHandler<PacketBombExplosion.Message, IMessage> {
/** Bomb type constants, matching the order bombs are handled in {@link electroblob.wizardry.CommonProxy}. */
public static final int FIREBOMB = 0;
public static final int POISON_BOMB = 1;
public static final int SMOKE_BOMB = 2;
public static final int SPARK_BOMB = 3;
@Override
public IMessage onMessage(Message message, MessageContext ctx){
if(ctx.side.isClient()){
net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(
() -> Wizardry.proxy.handleBombExplosionPacket(message));
}
return null;
}
public static class Message implements IMessage {
/** One of the bomb type constants defined in {@link PacketBombExplosion}. */
public int bombType;
public double x, y, z;
public float blastMultiplier;
/** Entity IDs of secondary targets struck by the spark bomb chain. Empty for other bomb types. */
public int[] secondaryTargetIDs;
// Required no-arg constructor
public Message(){}
public Message(int bombType, double x, double y, double z, float blastMultiplier){
this(bombType, x, y, z, blastMultiplier, new int[0]);
}
public Message(int bombType, double x, double y, double z, float blastMultiplier, int[] secondaryTargetIDs){
this.bombType = bombType;
this.x = x;
this.y = y;
this.z = z;
this.blastMultiplier = blastMultiplier;
this.secondaryTargetIDs = secondaryTargetIDs;
}
@Override
public void fromBytes(ByteBuf buf){
bombType = buf.readInt();
x = buf.readDouble();
y = buf.readDouble();
z = buf.readDouble();
blastMultiplier = buf.readFloat();
int count = buf.readInt();
secondaryTargetIDs = new int[count];
for(int i = 0; i < count; i++) secondaryTargetIDs[i] = buf.readInt();
}
@Override
public void toBytes(ByteBuf buf){
buf.writeInt(bombType);
buf.writeDouble(x);
buf.writeDouble(y);
buf.writeDouble(z);
buf.writeFloat(blastMultiplier);
buf.writeInt(secondaryTargetIDs.length);
for(int id : secondaryTargetIDs) buf.writeInt(id);
}
}
}
@@ -35,6 +35,7 @@ public class WizardryPacketHandler {
registerMessage(PacketSpellQuickAccess.class, PacketSpellQuickAccess.Message.class);
registerMessage(PacketRequestDonationPerks.class, PacketRequestDonationPerks.Message.class);
registerMessage(PacketSyncDonationPerks.class, PacketSyncDonationPerks.Message.class);
registerMessage(PacketBombExplosion.class, PacketBombExplosion.Message.class);
}
private static int nextPacketId = 0;
@@ -13,12 +13,15 @@ public class PotionDiamondflesh extends PotionMagicEffect {
super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/diamondflesh.png"));
// This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet.
this.setPotionName("potion." + Wizardry.MODID + ":ironflesh");
this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED,
"158a8af2-6db0-4340-a01c-a7b60d10ddf4", -0.1f, EntityUtils.Operations.MULTIPLY_CUMULATIVE);
// Only apply slowness if the setting allows it
if(Wizardry.settings.fleshSpellsCauseSlowness) {
this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED,
"158a8af2-6db0-4340-a01c-a7b60d10ddf4", -0.1f, EntityUtils.Operations.MULTIPLY_CUMULATIVE);
}
this.registerPotionAttributeModifier(SharedMonsterAttributes.ARMOR_TOUGHNESS,
"a68d4532-5847-426c-9b03-d541b113cec2", 3.0f, EntityUtils.Operations.ADD);
"a68d4532-5847-426c-9b03-d541b113cec2", (float)Wizardry.settings.diamondFleshArmorToughnessBonus, EntityUtils.Operations.ADD);
this.registerPotionAttributeModifier(SharedMonsterAttributes.ARMOR,
"46a095be-82dd-43fd-8b67-13f51591eb8e", 4.0f, EntityUtils.Operations.ADD);
"46a095be-82dd-43fd-8b67-13f51591eb8e", (float)Wizardry.settings.diamondFleshArmorBonus, EntityUtils.Operations.ADD);
}
@Override
@@ -0,0 +1,97 @@
package electroblob.wizardry.potion;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.init.SoundEvents;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.Blocks;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class PotionFireskin extends PotionMagicEffectParticles {
public PotionFireskin(boolean isBadEffect, int liquidColour, ResourceLocation texture) {
super(isBadEffect, liquidColour, texture);
this.setBeneficial();
}
@Override
public void spawnCustomParticle(World world, double x, double y, double z){
if(world.isRemote){
// Spawn ambient fire particles around the entity
world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0);
}
}
@Override
public boolean isReady(int duration, int amplifier) {
return true;
}
@Override
public void performEffect(EntityLivingBase entitylivingbase, int strength){
// Original fireskin behavior: extinguish the entity to prevent fire damage from melee attacks
entitylivingbase.extinguish();
// Server-side cobweb burning logic
World world = entitylivingbase.world;
if(!world.isRemote){
AxisAlignedBB entityBox = entitylivingbase.getEntityBoundingBox();
// Expand the bounding box slightly to catch cobwebs the entity is touching
entityBox = entityBox.expand(0.1, 0.1, 0.1);
BlockPos minPos = new BlockPos(entityBox.minX, entityBox.minY, entityBox.minZ);
BlockPos maxPos = new BlockPos(entityBox.maxX, entityBox.maxY, entityBox.maxZ);
boolean burnedCobweb = false;
// Check all blocks in the entity's bounding box
for(int x = minPos.getX(); x <= maxPos.getX(); x++){
for(int y = minPos.getY(); y <= maxPos.getY(); y++){
for(int z = minPos.getZ(); z <= maxPos.getZ(); z++){
BlockPos pos = new BlockPos(x, y, z);
if(world.getBlockState(pos).getBlock() == Blocks.WEB){
// Burn the cobweb
world.setBlockToAir(pos);
burnedCobweb = true;
// Add visual effects - spawn particles on client side
if(world.rand.nextFloat() < 0.7f){ // 70% chance to spawn particles for better visibility
float offsetX = world.rand.nextFloat() * 0.1f - 0.05f;
float offsetY = world.rand.nextFloat() * 0.1f + 0.05f;
float offsetZ = world.rand.nextFloat() * 0.1f - 0.05f;
// Main flame particle rising up
world.spawnParticle(EnumParticleTypes.FLAME,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
0, 0.1, 0);
// Two additional flame particles with random movement
world.spawnParticle(EnumParticleTypes.FLAME,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
offsetX, offsetY, offsetZ);
world.spawnParticle(EnumParticleTypes.FLAME,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
offsetX * -0.5f, offsetY * 0.8f, offsetZ * -0.5f);
}
}
}
}
}
// Play the fire extinguish sound only when cobwebs were actually burned
if(burnedCobweb){
world.playSound(null, entitylivingbase.getPosition(), SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.6f, 0.8f + world.rand.nextFloat() * 0.4f);
}
}
}
}
@@ -13,12 +13,15 @@ public class PotionIronflesh extends PotionMagicEffect {
super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/ironflesh.png"));
// This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet.
this.setPotionName("potion." + Wizardry.MODID + ":ironflesh");
this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED,
"fe607d55-50e3-4f4f-a959-6571503f92f4", -0.1f, EntityUtils.Operations.MULTIPLY_CUMULATIVE);
// Only apply slowness if the setting allows it
if(Wizardry.settings.fleshSpellsCauseSlowness) {
this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED,
"fe607d55-50e3-4f4f-a959-6571503f92f4", -0.1f, EntityUtils.Operations.MULTIPLY_CUMULATIVE);
}
this.registerPotionAttributeModifier(SharedMonsterAttributes.KNOCKBACK_RESISTANCE,
"6f78206e-8dd4-4d44-9792-d7a882111951", 0.3f, EntityUtils.Operations.ADD);
this.registerPotionAttributeModifier(SharedMonsterAttributes.ARMOR,
"e1adff1e-8510-4a09-96ed-ef677cad20c1", 4.0f, EntityUtils.Operations.ADD);
"e1adff1e-8510-4a09-96ed-ef677cad20c1", (float)Wizardry.settings.ironFleshArmorBonus, EntityUtils.Operations.ADD);
}
@Override
@@ -13,12 +13,15 @@ public class PotionOakflesh extends PotionMagicEffect {
super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/oakflesh.png"));
// This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet.
this.setPotionName("potion." + Wizardry.MODID + ":ironflesh");
this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED,
"98b4ba66-7c50-4a4c-9f3f-40bcb37313b5", -0.1f, EntityUtils.Operations.MULTIPLY_CUMULATIVE);
// Only apply slowness if the setting allows it
if(Wizardry.settings.fleshSpellsCauseSlowness) {
this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED,
"98b4ba66-7c50-4a4c-9f3f-40bcb37313b5", -0.1f, EntityUtils.Operations.MULTIPLY_CUMULATIVE);
}
this.registerPotionAttributeModifier(SharedMonsterAttributes.MAX_HEALTH,
"ed9d0423-60f4-4998-bd8d-dc7c33bd45b8", 0.2f, EntityUtils.Operations.MULTIPLY_FLAT);
"ed9d0423-60f4-4998-bd8d-dc7c33bd45b8", (float)Wizardry.settings.oakFleshHealthBonus, EntityUtils.Operations.MULTIPLY_FLAT);
this.registerPotionAttributeModifier(SharedMonsterAttributes.ARMOR,
"0b607c3f-fb14-43d7-96b5-1c1b6f6da242", 3.0f, EntityUtils.Operations.ADD);
"0b607c3f-fb14-43d7-96b5-1c1b6f6da242", (float)Wizardry.settings.oakFleshArmorBonus, EntityUtils.Operations.ADD);
}
@Override
@@ -2,6 +2,7 @@ package electroblob.wizardry.registry;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.potion.*;
import electroblob.wizardry.potion.PotionFireskin;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.EntityLivingBase;
@@ -95,19 +96,8 @@ public final class WizardryPotions {
}
}.setBeneficial()); // 0xffe89b
registerPotion(registry, "fireskin", new PotionMagicEffectParticles(false, 0,
new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/fireskin.png")){
@Override
public void spawnCustomParticle(World world, double x, double y, double z){
world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0);
}
@Override
public void performEffect(EntityLivingBase entitylivingbase, int strength){
entitylivingbase.extinguish(); // Stops melee mobs that are on fire from setting the player on fire,
// without allowing the player to actually stand in fire or swim in lava without taking damage.
}
}.setBeneficial()); // 0xff2f02
registerPotion(registry, "fireskin", new PotionFireskin(false, 0xff2f02,
new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/fireskin.png")));
registerPotion(registry, "ice_shroud", new PotionMagicEffectParticles(false, 0,
new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/ice_shroud.png")){
@@ -130,17 +120,7 @@ public final class WizardryPotions {
registerPotion(registry, "decay", new PotionDecay(true, 0x3c006c));
registerPotion(registry, "sixth_sense", new PotionMagicEffect(false, 0xc6ff01,
new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/sixth_sense.png")){
@Override
public void performEffect(EntityLivingBase target, int strength){
// Reset the shader (a bit dirty but both the potion expiry hooks are only fired server-side, and
// there's no point sending packets unnecessarily if we can just do this instead)
if(target.getActivePotionEffect(this).getDuration() <= 1 && target.world.isRemote
&& target == net.minecraft.client.Minecraft.getMinecraft().player){
net.minecraft.client.Minecraft.getMinecraft().entityRenderer.stopUseShader();
}
}
}.setBeneficial());
new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/sixth_sense.png")).setBeneficial());
registerPotion(registry, "arcane_jammer", new PotionMagicEffect(true, 0xcf4aa2,
new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/arcane_jammer.png")));
@@ -13,14 +13,18 @@ import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingDestroyBlockEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ExplosionEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class ArcaneLock extends SpellRay {
@@ -167,4 +171,8 @@ public class ArcaneLock extends SpellRay {
&& event.getWorld().getTileEntity(pos).getTileData().hasUniqueId(NBT_KEY));
}
}
@@ -27,7 +27,9 @@ public class BlindingFlash extends SpellAreaEffect {
if(EntityUtils.isLiving(target)){
int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
if(!world.isRemote){
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
}
}
return true;
@@ -27,9 +27,11 @@ public class Containment extends SpellRay {
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(EntityUtils.isLiving(target)){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.containment,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.containment,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
}
return true;
@@ -28,12 +28,14 @@ public class CurseOfEnfeeblement extends SpellRay {
if(EntityUtils.isLiving(target)){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_enfeeblement,
Integer.MAX_VALUE, getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
// Reduce the target's health to its new max health if necessary
if(((EntityLivingBase)target).getHealth() > ((EntityLivingBase)target).getMaxHealth()){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, MagicDamage.DamageType.WITHER),
((EntityLivingBase)target).getHealth() - ((EntityLivingBase)target).getMaxHealth());
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_enfeeblement,
Integer.MAX_VALUE, getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
// Reduce the target's health to its new max health if necessary
if(((EntityLivingBase)target).getHealth() > ((EntityLivingBase)target).getMaxHealth()){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, MagicDamage.DamageType.WITHER),
((EntityLivingBase)target).getHealth() - ((EntityLivingBase)target).getMaxHealth());
}
}
}
@@ -55,14 +55,16 @@ public class CurseOfSoulbinding extends SpellRay {
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(EntityUtils.isLiving(target) && caster instanceof EntityPlayer){
WizardData data = WizardData.get((EntityPlayer)caster);
if(data != null){
// Return false if soulbinding failed (e.g. if the target is already soulbound)
if(getSoulboundCreatures(data).add(target.getUniqueID())){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_soulbinding, Integer.MAX_VALUE));
}else{
return false;
if(!world.isRemote){
WizardData data = WizardData.get((EntityPlayer)caster);
if(data != null){
// Return false if soulbinding failed (e.g. if the target is already soulbound)
if(getSoulboundCreatures(data).add(target.getUniqueID())){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_soulbinding, Integer.MAX_VALUE));
}else{
return false;
}
}
}
}
@@ -28,8 +28,10 @@ public class CurseOfUndeath extends SpellRay {
if(EntityUtils.isLiving(target)){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE,
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE,
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
}
return true;
@@ -42,9 +42,11 @@ public class EmpoweringPresence extends SpellAreaEffect {
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
target.addPotionEffect(new PotionEffect(WizardryPotions.empowerment,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
if(!world.isRemote){
target.addPotionEffect(new PotionEffect(WizardryPotions.empowerment,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
}
}
return true;
@@ -74,4 +76,4 @@ public class EmpoweringPresence extends SpellAreaEffect {
}
}
}
}
@@ -33,7 +33,7 @@ public class FontOfMana extends SpellAreaEffect {
@Override
protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){
if(target instanceof EntityPlayer){ // Font of mana is only useful to players
if(target instanceof EntityPlayer && !world.isRemote){ // Font of mana is only useful to players
target.addPotionEffect(new PotionEffect(WizardryPotions.font_of_mana,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
(int)(getProperty(EFFECT_STRENGTH).intValue() + (modifiers.get(SpellModifiers.POTENCY) - 1) * 2)));
@@ -58,4 +58,4 @@ public class FontOfMana extends SpellAreaEffect {
/ (2 + event.getCaster().getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier()), false);
}
}
}
}
@@ -38,9 +38,11 @@ public class ForestsCurse extends SpellAreaEffect {
int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
int amplifier = (int)(getProperty(EFFECT_STRENGTH).floatValue() + bonusAmplifier);
target.addPotionEffect(new PotionEffect(MobEffects.POISON, duration, amplifier));
target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, duration, amplifier));
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, duration, amplifier));
if(!world.isRemote){
target.addPotionEffect(new PotionEffect(MobEffects.POISON, duration, amplifier));
target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, duration, amplifier));
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, duration, amplifier));
}
}
return true;
@@ -42,9 +42,11 @@ public class Freeze extends SpellRay {
if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage(
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
}else{
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
}
}
if(target.isBurning()) target.extinguish();
@@ -61,9 +61,11 @@ public class FrostRay extends SpellRay {
// with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry.
}else{
// For frost ray the entity can move slightly, unlike freeze
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
}
if(ticksInUse % 10 == 0){
float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY);
@@ -60,7 +60,7 @@ public class IceAge extends SpellAreaEffect {
caster, (int)(getProperty(FREEZE_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){
target.playSound(WizardrySounds.MISC_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
}
}else if(target instanceof EntityPlayer){
}else if(target instanceof EntityPlayer && !world.isRemote){
target.addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
@@ -52,12 +52,14 @@ public class Intimidate extends SpellAreaEffect {
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
NBTTagCompound entityNBT = target.getEntityData();
if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID());
if(!world.isRemote){
NBTTagCompound entityNBT = target.getEntityData();
if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID());
target.addPotionEffect(new PotionEffect(WizardryPotions.fear,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
target.addPotionEffect(new PotionEffect(WizardryPotions.fear,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
}
}
return true;
@@ -29,9 +29,11 @@ public class InvigoratingPresence extends SpellAreaEffect {
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
target.addPotionEffect(new PotionEffect(MobEffects.STRENGTH,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
if(!world.isRemote){
target.addPotionEffect(new PotionEffect(MobEffects.STRENGTH,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
}
return true;
}
@@ -32,7 +32,7 @@ public class MarkSacrifice extends SpellRay {
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(EntityUtils.isLiving(target)){
if(EntityUtils.isLiving(target) && !world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.mark_of_sacrifice,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
@@ -120,8 +120,10 @@ public class MindControl extends SpellRay {
}
public static void startControlling(EntityLiving target, EntityLivingBase controller, int duration){
target.getEntityData().setUniqueId(NBT_KEY, controller.getUniqueID());
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_control, duration, 0));
if(!target.world.isRemote){
target.getEntityData().setUniqueId(NBT_KEY, controller.getUniqueID());
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_control, duration, 0));
}
}
/**
@@ -238,4 +240,4 @@ public class MindControl extends SpellRay {
}
}
}
}
@@ -64,8 +64,10 @@ public class Paralysis extends SpellRay {
}
float durationMultiplier = target instanceof EntityPlayer ? modifiers.get(PLAYER_EFFECT_DURATION_MULTIPLIER) : 1.0f;
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.paralysis,
(int)(getProperty(EFFECT_DURATION).floatValue() * durationMultiplier * modifiers.get(WizardryItems.duration_upgrade)), 0));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.paralysis,
(int)(getProperty(EFFECT_DURATION).floatValue() * durationMultiplier * modifiers.get(WizardryItems.duration_upgrade)), 0));
}
}
return false;
@@ -34,9 +34,11 @@ public class PlagueOfDarkness extends SpellAreaEffect {
if(!MagicDamage.isEntityImmune(DamageType.WITHER, target)){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER),
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
target.addPotionEffect(new PotionEffect(MobEffects.WITHER,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
if(!world.isRemote){
target.addPotionEffect(new PotionEffect(MobEffects.WITHER,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
}
return true;
@@ -45,9 +45,11 @@ public class Poison extends SpellRay {
}else{
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON),
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.POISON,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.POISON,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
}
}
@@ -330,9 +330,11 @@ public class Possession extends SpellRay {
victim.setPosition(player.posX, player.posY, player.posZ);
if(!player.world.isRemote) player.world.spawnEntity(victim);
for(PotionEffect effect : player.getActivePotionEffects()){
if(effect.getPotion() instanceof PotionSlowTime) continue; // Don't transfer slow time
victim.addPotionEffect(effect);
if(!player.world.isRemote){
for(PotionEffect effect : player.getActivePotionEffects()){
if(effect.getPotion() instanceof PotionSlowTime) continue; // Don't transfer slow time
victim.addPotionEffect(effect);
}
}
}
@@ -69,8 +69,10 @@ public class RayOfPurification extends SpellRay {
EntityUtils.attackEntityWithoutKnockback(target,
MagicDamage.causeDirectMagicDamage(caster, DamageType.RADIANT), damage);
// Blindness
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.BLINDNESS,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.BLINDNESS,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))));
}
}
}
@@ -41,8 +41,10 @@ public class Shield extends Spell {
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, 10,
getProperty(EFFECT_STRENGTH).intValue(), false, false));
if(!world.isRemote){
caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, 10,
getProperty(EFFECT_STRENGTH).intValue(), false, false));
}
if(WizardData.get(caster).getVariable(SHIELD_KEY) == null){
@@ -35,9 +35,11 @@ public class SixthSense extends Spell {
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
caster.addPotionEffect(new PotionEffect(WizardryPotions.sixth_sense,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
(int)((modifiers.get(WizardryItems.range_upgrade) - 1f) / Constants.RANGE_INCREASE_PER_LEVEL)));
if(!world.isRemote){
caster.addPotionEffect(new PotionEffect(WizardryPotions.sixth_sense,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
(int)((modifiers.get(WizardryItems.range_upgrade) - 1f) / Constants.RANGE_INCREASE_PER_LEVEL)));
}
if(world.isRemote){
Wizardry.proxy.loadShader(caster, SHADER);
@@ -907,7 +907,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* its JSON file using a resource pack. If called with no arguments, defaults to any context, i.e. only returns
* false if the spell is completely disabled in all contexts. */
public final boolean isEnabled(SpellProperties.Context... contexts){
return enabled && (contexts.length == 0 || properties.isEnabled(contexts));
return enabled && (contexts.length == 0 || (arePropertiesInitialised() && properties.isEnabled(contexts)));
}
/** Sets whether the spell is enabled or not. */
@@ -168,6 +168,8 @@ public class SpellBuff extends Spell {
* Returns a boolean to allow subclasses to cause the spell to fail if for some reason the effect cannot be applied
* (for example, {@link Heal} fails if the caster is on full health). */
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
if(caster.world.isRemote) return true;
// This will generate 0 for novice and apprentice, and 1 for advanced and master
// TODO: Once we've found a way of detecting if amplifiers actually affect the potion type, implement it here.
int bonusAmplifier = getBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
@@ -47,7 +47,7 @@ import javax.annotation.Nullable;
public abstract class SpellRay extends Spell {
/** The distance below the caster's eyes that the bolt particles start from. */
protected static final double Y_OFFSET = 0.25;
public static final double Y_OFFSET = 0.25;
/** The distance between spawned particles. Defaults to 0.85. */
// 0.85 was chosen to keep it similar to the most common method used previously, which gave an effective spacing of
@@ -38,7 +38,7 @@ public class Telekinesis extends SpellRay {
} else if (target instanceof EntityPlayer && (Wizardry.settings.telekineticDisarmament && !ItemArtefact.isArtefactActive((EntityPlayer) target, WizardryItems.amulet_anchoring))) {
EntityPlayer player = (EntityPlayer) target;
EntityPlayer player = (EntityPlayer) target;
// IDEA: Disarm the offhand if the mainhand is empty or otherwise harmless?
@@ -157,8 +157,10 @@ public class Transportation extends Spell {
if(BlockTransportationStone.testForCircle(world, destination)){
this.playSound(world, player, 0, -1, modifiers);
player.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 150, 0));
data.setVariable(COUNTDOWN_KEY, getProperty(TELEPORT_COUNTDOWN).intValue());
if(!world.isRemote){
player.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 150, 0));
data.setVariable(COUNTDOWN_KEY, getProperty(TELEPORT_COUNTDOWN).intValue());
}
return true;
}else{
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".missing"), true);
@@ -39,9 +39,11 @@ public class Wither extends SpellRay {
}else{
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER),
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
}
}
@@ -67,7 +67,9 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory,
if(stack.getItem() instanceof IManaStoringItem && !this.world.isRemote && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
&& this.world.getTotalWorldTime() % electroblob.wizardry.constants.Constants.CONDENSER_TICK_INTERVAL == 0){
// If the upgrade level is 0, this does nothing anyway.
((IManaStoringItem)stack.getItem()).rechargeMana(stack, WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade));
int baseAmount = WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade);
int amount = (int)(baseAmount * Wizardry.settings.condenserAmountMultiplier);
((IManaStoringItem)stack.getItem()).rechargeMana(stack, amount);
}
// The server doesn't care what these are, and there's no need for them to be synced or saved.
@@ -235,12 +235,11 @@ public class TileEntityImbuementAltar extends TileEntity implements ITickable {
*/
public static ItemStack getImbuementResult(ItemStack input, Element[] receptacleElements, boolean fullLootGen, World world, EntityPlayer lastUser){
ItemStack eventResult = ItemStack.EMPTY;
ImbuementActivateEvent event = new ImbuementActivateEvent(input, receptacleElements, world, lastUser, ItemStack.EMPTY);
if (world != null && MinecraftForge.EVENT_BUS.post(new ImbuementActivateEvent(input, receptacleElements, world, lastUser, eventResult))) {
// return the stack if something changes the result from an empty stack.
//noinspection ConstantConditions
if (eventResult != ItemStack.EMPTY) return eventResult;
if (world != null && MinecraftForge.EVENT_BUS.post(event)) {
// Event was cancelled; return whatever result the handler set (may be empty to suppress all recipes).
return event.result;
}
if(input.getItem() instanceof ItemWizardArmour && ((ItemWizardArmour)input.getItem()).element == null){
@@ -27,7 +27,7 @@ public class TileEntityReceptacle extends TileEntity {
public void setElement(@Nullable Element element){
this.element = element;
world.notifyNeighborsRespectDebug(pos, blockType, true); // Update altar if connected
world.notifyNeighborsRespectDebug(pos, getBlockType(), true); // Update altar if connected
world.checkLight(pos);
}
@@ -2,6 +2,7 @@ package electroblob.wizardry.tileentity;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockPedestal;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.packet.PacketConquerShrine;
@@ -13,87 +14,120 @@ import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.ArcaneLock;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.util.ITickable;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.*;
public class TileEntityShrineCore extends TileEntity implements ITickable {
private static final double ACTIVATION_RADIUS = 5;
private boolean activated = false;
private boolean conquered = false;
private long regenerationTime = 0;
private long lastRegenerationTime = 0;
private Element shrineElement;
private AxisAlignedBB containmentField;
private final UUID[] linkedWizards = new UUID[3];
private TileEntity linkedContainer;
private BlockPos linkedContainerPos; // Temporary stores the container position read from NBT until the world is set
private final Set<UUID> lootedPlayers = new HashSet<>();
@Override
public void setPos(BlockPos pos){
public void setPos(BlockPos pos) {
super.setPos(pos);
initContainmentField(pos);
}
private void initContainmentField(BlockPos pos){
private void initContainmentField(BlockPos pos) {
float r = PotionContainment.getContainmentDistance(0);
this.containmentField = new AxisAlignedBB(-r, -r, -r, r, r, r).offset(GeometryUtils.getCentre(pos));
}
public void linkContainer(TileEntity container){
public void linkContainer(TileEntity container) {
this.linkedContainer = container;
}
@Override
public void update(){
public void setShrineElement(Element element) {
this.shrineElement = element;
}
if(this.linkedContainer == null && this.linkedContainerPos != null){
public boolean canPlayerLoot(EntityPlayer player) {
if (Wizardry.settings.shrineAllowMultipleLoot) {
return true; // Allow multiple looting if enabled
}
return !lootedPlayers.contains(player.getUniqueID());
}
public void recordPlayerLoot(EntityPlayer player) {
if (!Wizardry.settings.shrineAllowMultipleLoot) {
lootedPlayers.add(player.getUniqueID());
this.markDirty();
}
}
@Override
public void update() {
if (this.linkedContainer == null && this.linkedContainerPos != null) {
this.linkContainer(world.getTileEntity(this.linkedContainerPos));
}
// Handle shrine regeneration
if (conquered && Wizardry.settings.shrineRegenerationEnabled && regenerationTime > 0) {
if (world.getTotalWorldTime() >= regenerationTime) {
regenerate();
return; // Don't process other logic during regeneration
}
}
double x = this.pos.getX() + 0.5;
double y = this.pos.getY() + 0.5;
double z = this.pos.getZ() + 0.5;
if(!activated && world.getClosestPlayer(x, y, z, ACTIVATION_RADIUS, false) != null){
if (!activated && !conquered && world.getClosestPlayer(x, y, z, ACTIVATION_RADIUS, false) != null && (lastRegenerationTime == 0 || world.getTotalWorldTime() - lastRegenerationTime > 100)) { // 5 second delay after regeneration
this.activated = true;
if(world.isRemote){
if (world.isRemote) {
ParticleBuilder.create(Type.SPHERE).pos(x, y + 1, z).clr(0xf06495).scale(5).time(12).spawn(world);
}
world.playSound(x, y, z,
WizardrySounds.BLOCK_PEDESTAL_ACTIVATE, SoundCategory.BLOCKS, 1.5f, 1, false);
world.playSound(x, y, z, WizardrySounds.BLOCK_PEDESTAL_ACTIVATE, SoundCategory.BLOCKS, 1.5f, 1, false);
if(!world.isRemote){
if (!world.isRemote) {
EntityEvilWizard[] wizards = new EntityEvilWizard[linkedWizards.length];
for(int i = 0; i < linkedWizards.length; i++){
for (int i = 0; i < linkedWizards.length; i++) {
EntityEvilWizard wizard = new EntityEvilWizard(world);
float angle = world.rand.nextFloat() * 2 * (float)Math.PI;
float angle = world.rand.nextFloat() * 2 * (float) Math.PI;
double x1 = this.pos.getX() + 0.5 + 5 * MathHelper.sin(angle);
double z1 = this.pos.getZ() + 0.5 + 5 * MathHelper.cos(angle);
Integer y1 = BlockUtils.getNearestFloor(world, new BlockPos(x1, this.pos.getY(), z1), 8);
if(y1 == null){
if (y1 == null) {
// Fallback to the position of the shrine core if it failed to find a position (unlikely)
x1 = this.pos.getX() + 1; // Offset it so the wizard isn't inside the block
y1 = this.pos.getY();
@@ -110,110 +144,279 @@ public class TileEntityShrineCore extends TileEntity implements ITickable {
linkedWizards[i] = wizard.getUniqueID();
}
for(EntityEvilWizard wizard : wizards) wizard.groupUUIDs.addAll(Arrays.asList(linkedWizards));
for (EntityEvilWizard wizard : wizards) wizard.groupUUIDs.addAll(Arrays.asList(linkedWizards));
}
containNearbyTargets();
}
if(activated && world.getTotalWorldTime() % 20L == 0) containNearbyTargets();
if (!areWizardsDead() && activated && world.getTotalWorldTime() % 20L == 0) containNearbyTargets();
if(activated && areWizardsDead() && !world.isRemote){
if (activated && areWizardsDead() && !world.isRemote) {
conquer();
}
}
private boolean areWizardsDead(){
private boolean areWizardsDead() {
for(UUID uuid : linkedWizards){
for (UUID uuid : linkedWizards) {
Entity entity = EntityUtils.getEntityByUUID(world, uuid);
if(entity instanceof EntityEvilWizard && entity.isEntityAlive()) return false;
if (entity instanceof EntityEvilWizard && entity.isEntityAlive()) return false;
}
return true;
}
public void conquer(){
public void conquer() {
double x = this.pos.getX() + 0.5;
double y = this.pos.getY() + 0.5;
double z = this.pos.getZ() + 0.5;
if(!world.isRemote){
if (!world.isRemote) {
WizardryPacketHandler.net.sendToAllAround(new PacketConquerShrine.Message(this.pos),
new NetworkRegistry.TargetPoint(this.world.provider.getDimension(), x, y, z, 64));
WizardryPacketHandler.net.sendToAllAround(new PacketConquerShrine.Message(this.pos), new NetworkRegistry.TargetPoint(this.world.provider.getDimension(), x, y, z, 64));
if(world.getBlockState(pos).getBlock() == WizardryBlocks.runestone_pedestal){
world.setBlockState(pos, WizardryBlocks.runestone_pedestal.getDefaultState()
.withProperty(BlockPedestal.ELEMENT, world.getBlockState(pos).getValue(BlockPedestal.ELEMENT)));
}else{
Wizardry.logger.warn("What's going on?! A shrine core is being conquered but the block at its position is not a runestone pedestal!");
// Remove containment effects from nearby targets when shrine is conquered
removeContainmentFromNearbyTargets();
// If regeneration is enabled, schedule regeneration instead of removing the tile entity
if (Wizardry.settings.shrineRegenerationEnabled) {
this.conquered = true;
this.regenerationTime = world.getTotalWorldTime() + (Wizardry.settings.shrineRegenerationTime * 1200L); // Convert minutes to ticks (20 ticks per second * 60 seconds)
this.activated = false;
Arrays.fill(this.linkedWizards, null); // Clear wizard references
// Keep lootedPlayers list - it's permanent tracking for this shrine instance
// Handle chest breaking and loot dropping
handleChestLootDrop();
// Mark the tile entity for update
this.markDirty();
if (world.getBlockState(pos).getBlock() == WizardryBlocks.runestone_pedestal) {
// Keep the block state the same, just mark as conquered
this.shrineElement = world.getBlockState(pos).getValue(BlockPedestal.ELEMENT);
} else {
Wizardry.logger.warn("What's going on?! A shrine core is being conquered but the block at its position is not a runestone pedestal!");
}
} else {
// Original behavior: remove the tile entity
BlockPos chestPos = this.pos.up();
TileEntity chestTileEntity = world.getTileEntity(chestPos);
if (chestTileEntity instanceof TileEntityChest) {
TileEntityChest chest = (TileEntityChest) chestTileEntity;
NBTExtras.removeUniqueId(chest.getTileData(), ArcaneLock.NBT_KEY);
chest.markDirty();
world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3);
}
if (world.getBlockState(pos).getBlock() == WizardryBlocks.runestone_pedestal) {
world.setBlockState(pos, WizardryBlocks.runestone_pedestal.getDefaultState().withProperty(BlockPedestal.ELEMENT, world.getBlockState(pos).getValue(BlockPedestal.ELEMENT)));
} else {
Wizardry.logger.warn("What's going on?! A shrine core is being conquered but the block at its position is not a runestone pedestal!");
}
world.markTileEntityForRemoval(this);
}
}
world.markTileEntityForRemoval(this);
if(!world.isRemote){
if(linkedContainer != null) NBTExtras.removeUniqueId(linkedContainer.getTileData(), ArcaneLock.NBT_KEY);
}else{
TileEntity tileEntity = world.getTileEntity(this.pos.up());
if(tileEntity != null){ // Bit of a dirty fix but it's only visual, so meh
NBTExtras.removeUniqueId(tileEntity.getTileData(), ArcaneLock.NBT_KEY);
}
if (linkedContainer == null) {
linkedContainer = world.getTileEntity(pos.up());
}
if (linkedContainer != null) {
NBTExtras.removeUniqueId(linkedContainer.getTileData(), ArcaneLock.NBT_KEY);
//linkedContainer.getTileData().removeTag("arcaneLockOwnerMost");
//linkedContainer.getTileData().removeTag("arcaneLockOwnerLeast");
}
world.playSound(x, y, z, WizardrySounds.BLOCK_PEDESTAL_CONQUER, SoundCategory.BLOCKS, 1, 1, false);
if(world.isRemote){
if (world.isRemote) {
ParticleBuilder.create(Type.SPHERE).scale(5).pos(x, y + 1, z).clr(0xf06495).time(12).spawn(world);
for(int i=0; i<5; i++){
for (int i = 0; i < 5; i++) {
float brightness = 0.8f + world.rand.nextFloat() * 0.2f;
ParticleBuilder.create(Type.SPARKLE, world.rand, x, y + 1, z, 1, true)
.clr(1, brightness, brightness).spawn(world);
ParticleBuilder.create(Type.SPARKLE, world.rand, x, y + 1, z, 1, true).clr(1, brightness, brightness).spawn(world);
}
}
}
private void containNearbyTargets(){
private void regenerate() {
List<EntityLivingBase> entities = world.getEntitiesWithinAABB(EntityLivingBase.class, containmentField,
e -> e instanceof EntityPlayer || e instanceof EntityWizard || e instanceof EntityEvilWizard);
if (world.isRemote) return;
for(EntityLivingBase entity : entities){
// Remove containment effects from nearby targets when shrine regenerates
removeContainmentFromNearbyTargets();
// Reset shrine state
this.conquered = false;
this.regenerationTime = 0;
this.activated = false;
Arrays.fill(this.linkedWizards, null);
// Keep lootedPlayers list - it's permanent for this shrine instance
// Force a short delay before allowing reactivation to prevent immediate re-activation
this.lastRegenerationTime = world.getTotalWorldTime();
// Forcibly replace whatever block is above the altar with a fresh chest
BlockPos chestPos = this.pos.up();
// Always replace the block above with a fresh chest during regeneration
world.setBlockState(chestPos, Blocks.CHEST.getDefaultState());
// Set up the loot table for the shrine chest
TileEntity chestTileEntity = world.getTileEntity(chestPos);
if (chestTileEntity instanceof TileEntityChest) {
TileEntityChest chest = (TileEntityChest) chestTileEntity;
chest.setLootTable(new ResourceLocation(Wizardry.MODID, "chests/shrine"), world.rand.nextLong());
}
// Link and apply arcane lock to the container
if (chestTileEntity != null) {
this.linkContainer(chestTileEntity);
chestTileEntity.getTileData().setUniqueId(ArcaneLock.NBT_KEY, new UUID(0, 0)); // Nil UUID for shrine lock
chestTileEntity.markDirty(); // Mark tile entity as dirty for client sync
// Trigger visual update for arcane lock effect
IBlockState blockState = world.getBlockState(chestPos);
world.markAndNotifyBlock(chestPos, null, blockState, blockState, 3);
world.notifyBlockUpdate(chestPos, blockState, blockState, 3); // Additional client sync
}
// Visual and audio effects for regeneration
double x = this.pos.getX() + 0.5;
double y = this.pos.getY() + 0.5;
double z = this.pos.getZ() + 0.5;
// WizardryPacketHandler.net.sendToAllAround(new PacketConquerShrine.Message(this.pos), new NetworkRegistry.TargetPoint(this.world.provider.getDimension(), x, y, z, 64));
if (world.isRemote) {
ParticleBuilder.create(Type.SPHERE).pos(x, y + 1, z).clr(0xf06495).scale(5).time(12).spawn(world);
}
world.playSound(x, y, z, WizardrySounds.BLOCK_PEDESTAL_ACTIVATE, SoundCategory.BLOCKS, 1.5f, 1, false);
this.markDirty();
}
private void handleChestLootDrop() {
BlockPos chestPos = this.pos.up();
// Check if there's a chest at the expected position
if (world.getBlockState(chestPos).getBlock() == Blocks.CHEST) {
TileEntity chestTileEntity = world.getTileEntity(chestPos);
if (chestTileEntity instanceof TileEntityChest) {
TileEntityChest chest = (TileEntityChest) chestTileEntity;
// Find the player who conquered the shrine (closest player)
EntityPlayer conqueringPlayer = world.getClosestPlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 20.0, false);
if (conqueringPlayer != null && !canPlayerLoot(conqueringPlayer)) {
// Send message to player that they've already looted this shrine
if (!world.isRemote) {
conqueringPlayer.sendMessage(new TextComponentTranslation("wizardry.shrine_already_looted"));
}
chest.setLootTable(null, world.rand.nextLong());
if (world.getBlockState(chestPos).getBlock() == Blocks.CHEST) {
world.setBlockToAir(chestPos);
}
return;
}
if (conqueringPlayer != null && canPlayerLoot(conqueringPlayer)) {
// Record that this player has looted the shrine
recordPlayerLoot(conqueringPlayer);
}
// If player has already looted, don't drop anything
}
}
// Break the chest regardless
if (world.getBlockState(chestPos).getBlock() == Blocks.CHEST) {
world.setBlockToAir(chestPos);
}
}
private void containNearbyTargets() {
List<EntityLivingBase> entities = world.getEntitiesWithinAABB(EntityLivingBase.class, containmentField, e -> e instanceof EntityPlayer || e instanceof EntityWizard || e instanceof EntityEvilWizard);
for (EntityLivingBase entity : entities) {
entity.addPotionEffect(new PotionEffect(WizardryPotions.containment, 219));
NBTExtras.storeTagSafely(entity.getEntityData(), PotionContainment.ENTITY_TAG, NBTUtil.createPosTag(this.pos));
}
}
private void removeContainmentFromNearbyTargets() {
// List<EntityLivingBase> entities = world.getEntitiesWithinAABB(EntityLivingBase.class, containmentField,
// e -> e instanceof EntityPlayer || e instanceof EntityWizard || e instanceof EntityEvilWizard);
//
// for(EntityLivingBase entity : entities){
// // Remove the containment potion effect
// if(entity.isPotionActive(WizardryPotions.containment)){
// entity.removePotionEffect(WizardryPotions.containment);
// }
// // Also remove the containment position tag
// if(entity.getEntityData().hasKey(PotionContainment.ENTITY_TAG)){
// BlockPos containmentPos = NBTUtil.getPosFromTag(entity.getEntityData().getCompoundTag(PotionContainment.ENTITY_TAG));
// // Only remove if the containment position matches this shrine
// if(containmentPos.equals(this.pos)){
// entity.getEntityData().removeTag(PotionContainment.ENTITY_TAG);
// }
// }
// }
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound compound){
public NBTTagCompound writeToNBT(NBTTagCompound compound) {
compound.setBoolean("activated", this.activated);
if(linkedContainer != null) NBTExtras.storeTagSafely(compound, "linkedContainerPos", NBTUtil.createPosTag(linkedContainer.getPos()));
compound.setBoolean("conquered", this.conquered);
compound.setLong("regenerationTime", this.regenerationTime);
compound.setLong("lastRegenerationTime", this.lastRegenerationTime);
if (shrineElement != null) compound.setInteger("shrineElement", this.shrineElement.ordinal());
if (linkedContainer != null)
NBTExtras.storeTagSafely(compound, "linkedContainerPos", NBTUtil.createPosTag(linkedContainer.getPos()));
NBTTagList tagList = new NBTTagList();
for(UUID uuid : linkedWizards){
if(uuid != null) tagList.appendTag(NBTUtil.createUUIDTag(uuid));
NBTTagList wizardTagList = new NBTTagList();
for (UUID uuid : linkedWizards) {
if (uuid != null) wizardTagList.appendTag(NBTUtil.createUUIDTag(uuid));
}
NBTExtras.storeTagSafely(compound, "wizards", tagList);
NBTExtras.storeTagSafely(compound, "wizards", wizardTagList);
NBTTagList playerTagList = new NBTTagList();
for (UUID uuid : lootedPlayers) {
playerTagList.appendTag(NBTUtil.createUUIDTag(uuid));
}
NBTExtras.storeTagSafely(compound, "lootedPlayers", playerTagList);
return super.writeToNBT(compound);
}
@Override
public void readFromNBT(NBTTagCompound compound){
public void readFromNBT(NBTTagCompound compound) {
this.activated = compound.getBoolean("activated");
this.conquered = compound.getBoolean("conquered");
this.regenerationTime = compound.getLong("regenerationTime");
this.lastRegenerationTime = compound.getLong("lastRegenerationTime");
if (compound.hasKey("shrineElement"))
this.shrineElement = Element.values()[compound.getInteger("shrineElement")];
this.linkedContainerPos = NBTUtil.getPosFromTag(compound.getCompoundTag("linkedContainerPos"));
NBTTagList tagList = compound.getTagList("wizards", Constants.NBT.TAG_COMPOUND);
NBTTagList wizardTagList = compound.getTagList("wizards", Constants.NBT.TAG_COMPOUND);
int i = 0;
for(NBTBase tag : tagList){
if(tag instanceof NBTTagCompound) linkedWizards[i++] = NBTUtil.getUUIDFromTag((NBTTagCompound)tag);
for (NBTBase tag : wizardTagList) {
if (tag instanceof NBTTagCompound) linkedWizards[i++] = NBTUtil.getUUIDFromTag((NBTTagCompound) tag);
else Wizardry.logger.warn("Unexpected tag type in NBT tag list of compound tags!");
}
this.lootedPlayers.clear();
if (compound.hasKey("lootedPlayers")) {
NBTTagList playerTagList = compound.getTagList("lootedPlayers", Constants.NBT.TAG_COMPOUND);
for (NBTBase tag : playerTagList) {
if (tag instanceof NBTTagCompound) lootedPlayers.add(NBTUtil.getUUIDFromTag((NBTTagCompound) tag));
}
}
super.readFromNBT(compound);
// Must be after super
initContainmentField(this.pos);
@@ -12,16 +12,105 @@ import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiPredicate;
/**
* Contains some useful static methods for interacting with the ally designation system. Also handles the friendly fire
* setting. This was split off from {@code WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to
* navigate.
* <p></p>
* <b>Addon API:</b> External mods can extend the ally system without modifying wizardry by registering predicates via
* {@link #registerValidTargetPredicate(BiPredicate)} and {@link #registerAllyPredicate(BiPredicate)}. Predicates are
* registered once at mod initialisation and called on every ally check with the live entity references, so any
* dynamic ownership model (NBT, capabilities, in-memory maps) is supported. Predicates run after all built-in
* wizardry logic and are additive they can protect additional entity pairs but cannot remove existing protections
* such as direct ownership or scoreboard teams.
*/
@Mod.EventBusSubscriber
public final class AllyDesignationSystem {
private AllyDesignationSystem(){} // No instances!
/**
* Predicates registered by external mods for {@link #isValidTarget}. Using {@link CopyOnWriteArrayList} so that
* registration from any thread during mod initialisation is safe, while iteration on the server tick thread is
* lock-free.
*/
private static final List<BiPredicate<Entity, Entity>> validTargetPredicates = new CopyOnWriteArrayList<>();
/**
* Predicates registered by external mods for {@link #isAllied}. See {@link #validTargetPredicates}.
*/
private static final List<BiPredicate<EntityLivingBase, EntityLivingBase>> allyPredicates = new CopyOnWriteArrayList<>();
/**
* Registers a predicate that extends the ally check performed by {@link #isValidTarget(Entity, Entity)}.
* <p></p>
* Call this method <b>once</b> during your mod's initialisation (e.g. {@code FMLInitializationEvent}). The
* predicate is stored statically and invoked on every {@code isValidTarget} call, so it must be cheap avoid
* world queries or heavy computation inside the lambda.
* <p></p>
* <b>Predicate semantics:</b> return {@code true} to declare that {@code target} should be treated as an ally of
* {@code attacker} and therefore must not be attacked. Return {@code false} to defer to other registered
* predicates or the default result.
* <p></p>
* <b>Scope:</b> Predicates run after all built-in wizardry rules (direct ownership, scoreboard teams, player ally
* list, mind-control, etc.) and only when none of those rules already produced a result. They are therefore
* additive they can protect additional pairs but cannot override existing protections.
* <p></p>
* <b>Recursion note:</b> {@code isValidTarget} recurses automatically for owned attackers (e.g. a summoned zombie
* delegates to its owner). Predicates are only invoked at the root of that recursion (i.e. when the attacker is
* not itself an {@link IEntityOwnable}), so each logical query triggers the predicate exactly once regardless of
* ownership chain depth.
* <p></p>
* <b>Example</b> protecting minions of allied players in an external mod that tracks ownership via a capability:
* <pre>{@code
* AllyDesignationSystem.registerValidTargetPredicate((attacker, target) -> {
* UUID summonerOfTarget = MyMod.getOwnerUUID(target); // reads your capability / NBT
* if (summonerOfTarget == null) return false; // not one of our mobs
* UUID summonerOfAttacker = MyMod.getOwnerUUID(attacker);
* if (summonerOfAttacker == null) return false;
* // Protect if both are owned by the same player, or by players who are wizardry allies
* if (summonerOfTarget.equals(summonerOfAttacker)) return true;
* EntityPlayer ownerA = ...; EntityPlayer ownerB = ...;
* return AllyDesignationSystem.isPlayerAlly(ownerA, ownerB);
* });
* }</pre>
*
* @param predicate A {@link BiPredicate} receiving {@code (attacker, target)}. Must be non-null and thread-safe.
*/
public static void registerValidTargetPredicate(BiPredicate<Entity, Entity> predicate){
validTargetPredicates.add(predicate);
}
/**
* Registers a predicate that extends the ally check performed by {@link #isAllied(EntityLivingBase, EntityLivingBase)}.
* <p></p>
* Call this method <b>once</b> during your mod's initialisation (e.g. {@code FMLInitializationEvent}). The
* predicate is stored statically and invoked on every {@code isAllied} call.
* <p></p>
* <b>Predicate semantics:</b> return {@code true} to declare that {@code possibleAlly} should be considered allied
* with {@code allyOf}. Returning {@code true} affects any system that calls {@code isAllied}, including healing
* AoE spells, {@code EntityRadiantTotem}, and the friendly-fire event handler. Return {@code false} to defer.
* <p></p>
* <b>Scope:</b> Same additive-only guarantee as {@link #registerValidTargetPredicate} predicates run after all
* built-in rules and cannot remove existing ally relationships.
* <p></p>
* <b>Example</b> treating faction members as allies for healing spells:
* <pre>{@code
* AllyDesignationSystem.registerAllyPredicate((allyOf, possibleAlly) ->
* MyFactionMod.sameFaction(allyOf, possibleAlly)
* );
* }</pre>
*
* @param predicate A {@link BiPredicate} receiving {@code (allyOf, possibleAlly)}. Must be non-null and thread-safe.
*/
public static void registerAllyPredicate(BiPredicate<EntityLivingBase, EntityLivingBase> predicate){
allyPredicates.add(predicate);
}
/** Set of constants for each of the four friendly fire settings. */
public enum FriendlyFire {
@@ -117,6 +206,9 @@ public final class AllyDesignationSystem {
// Always return true if the attacker is null - this must be after the target null check!
if(attacker == null) return true;
// Teammates are allies
if(attacker.isOnSameTeam(target)) return false;
// Tests whether the target is the attacker
if(target == attacker) return false;
@@ -194,6 +286,15 @@ public final class AllyDesignationSystem {
}
}
// Addon predicate registry only evaluated at the root of the ownership recursion (i.e. when the attacker is
// not itself an IEntityOwnable). Owned attackers already delegate upward via the recursive call at the top of
// this method, so skipping them here ensures each logical query fires the predicates exactly once.
if(!validTargetPredicates.isEmpty() && !(attacker instanceof IEntityOwnable)){
for(BiPredicate<Entity, Entity> predicate : validTargetPredicates){
if(predicate.test(attacker, target)) return false;
}
}
return true;
}
@@ -203,6 +304,10 @@ public final class AllyDesignationSystem {
* generally used to determine targets for healing or other group buffs. */
public static boolean isAllied(EntityLivingBase allyOf, EntityLivingBase possibleAlly){
if (allyOf == null || possibleAlly == null) {
return false;
}
// Owned entities inherit their owner's allies
if(allyOf instanceof IEntityOwnable){
Entity owner = ((IEntityOwnable)allyOf).getOwner();
@@ -220,6 +325,27 @@ public final class AllyDesignationSystem {
if(allyOf instanceof EntityPlayer && isOwnerAlly((EntityPlayer)allyOf, pet)) return true;
}
if(possibleAlly.isOnSameTeam(allyOf)) {
return true;
}
// Check if the possibleAlly is mind controlled by the allyOf entity
if(possibleAlly instanceof EntityLiving && possibleAlly.isPotionActive(WizardryPotions.mind_control)){
NBTTagCompound entityNBT = possibleAlly.getEntityData();
if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){
Entity controller = EntityUtils.getEntityByUUID(possibleAlly.world, entityNBT.getUniqueId(MindControl.NBT_KEY));
return controller == allyOf;
}
}
// Addon predicate registry runs after all built-in rules; additive only.
if(!allyPredicates.isEmpty()){
for(BiPredicate<EntityLivingBase, EntityLivingBase> predicate : allyPredicates){
if(predicate.test(allyOf, possibleAlly)) return true;
}
}
return false;
}
@@ -237,7 +363,10 @@ public final class AllyDesignationSystem {
WizardData data = WizardData.get(allyOf);
if(data == null) return false;
Entity owner = ownable.getOwner();
return owner instanceof EntityPlayer ? data.isPlayerAlly((EntityPlayer)owner) : data.isPlayerAlly(ownable.getOwnerId());
if(owner == null) return data.isPlayerAlly(ownable.getOwnerId()); // offline owner
if(owner instanceof EntityPlayer) return data.isPlayerAlly((EntityPlayer)owner);
if(owner instanceof IEntityOwnable) return isOwnerAlly(allyOf, (IEntityOwnable)owner); // recurse
return false;
}
@SubscribeEvent
@@ -74,13 +74,27 @@ public class WorldGenShrine extends WorldGenSurfaceStructure {
if(container != null){
container.getTileData().setUniqueId(ArcaneLock.NBT_KEY, new UUID(0, 0)); // Nil UUID
container.markDirty(); // Mark tile entity as dirty for client sync
if(core instanceof TileEntityShrineCore){
((TileEntityShrineCore)core).linkContainer(container);
}else{
Wizardry.logger.info("What?!");
// Trigger visual update for arcane lock effect
BlockPos chestPos = entry.getKey().up();
net.minecraft.block.state.IBlockState blockState = world.getBlockState(chestPos);
world.markAndNotifyBlock(chestPos, null, blockState, blockState, 3);
world.notifyBlockUpdate(chestPos, blockState, blockState, 3); // Additional client sync
// Set up the loot table for the shrine chest
if(container instanceof net.minecraft.tileentity.TileEntityChest){
net.minecraft.tileentity.TileEntityChest chest = (net.minecraft.tileentity.TileEntityChest) container;
chest.setLootTable(new net.minecraft.util.ResourceLocation(Wizardry.MODID, "chests/shrine"), world.rand.nextLong());
}
if(core instanceof TileEntityShrineCore){
((TileEntityShrineCore)core).linkContainer(container);
((TileEntityShrineCore)core).setShrineElement(element);
}else{
Wizardry.logger.info("What?!");
}
}else{
Wizardry.logger.info("Expected chest or other container at {} in structure {}, found no tile entity", entry.getKey(), structureFile);
}
@@ -1731,6 +1731,8 @@ config.ebwizardry.mobs_immune_to_ice=Mobs Immune To Ice
config.ebwizardry.mobs_immune_to_ice.tooltip=List of names of entities that are immune to ice, in addition to the defaults. Add mod creatures to this list if you want them to be immune to ice magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_lightning=Mobs Immune To Lightning
config.ebwizardry.mobs_immune_to_lightning.tooltip=List of names of entities that are immune to lightning, in addition to the defaults. Add mod creatures to this list if you want them to be immune to lightning magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_magic=Mobs Immune To Magic
config.ebwizardry.mobs_immune_to_magic.tooltip=List of names of entities that are immune to magic, in addition to the defaults. Add mod creatures to this list if you want them to be immune to magic damage and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_wither=Mobs Immune To Wither
config.ebwizardry.mobs_immune_to_wither.tooltip=List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_poison=Mobs Immune To Poison
@@ -524,6 +524,7 @@ item.ebwizardry\:charm_move_speed.name=Icarus Medallion
item.ebwizardry\:charm_move_speed.desc=Move at near-full speed when casting spells
item.ebwizardry\:charm_spell_discovery.name=Archivist's Eyeglass
item.ebwizardry\:charm_spell_discovery.desc=Spell books found in chests are much more likely to be ones you have not yet discovered
item.ebwizardry\:charm_spell_discovery.desc.color=When equipped, you can see the element of a known spell book by the color of its bookmark
item.ebwizardry\:charm_auto_smelt.name=Metallurgist's Mark
item.ebwizardry\:charm_auto_smelt.desc=Pocket furnace triggers automatically when bound to a wand on your hotbar
item.ebwizardry\:charm_lava_walking.name=Nether Ice Core
@@ -1541,6 +1542,22 @@ config.ebwizardry.single_use_spell_books=Spell Books Are Consumed By Wands
config.ebwizardry.single_use_spell_books.tooltip=Whether spell books are consumed when they are bound to a wand.
config.ebwizardry.prevent_binding_same_spell_twice_to_wands=Prevent Binding The Same Spell To A Wand Multiple Times
config.ebwizardry.prevent_binding_same_spell_twice_to_wands.tooltip=Whether to prevent binding the same spell to a wand multiple times
config.ebwizardry.mana_per_shard=Mana Per Crystal Shard
config.ebwizardry.mana_per_shard.tooltip=The amount of mana a crystal shard is worth.
config.ebwizardry.mana_per_crystal=Mana Per Crystal
config.ebwizardry.mana_per_crystal.tooltip=The amount of mana each magic crystal is worth.
config.ebwizardry.grand_crystal_mana=Grand Crystal Mana
config.ebwizardry.grand_crystal_mana.tooltip=The amount of mana a grand magic crystal is worth.
config.ebwizardry.upgrade_stack_limit=Upgrade Stack Limit
config.ebwizardry.upgrade_stack_limit.tooltip=The maximum number of one type of wand upgrade which can be applied to a wand.
config.ebwizardry.non_elemental_upgrade_bonus=Non-Elemental Upgrade Bonus
config.ebwizardry.non_elemental_upgrade_bonus.tooltip=The bonus amount of wand upgrades that can be applied to a non-elemental wand.
config.ebwizardry.siphon_mana_per_level=Siphon Mana Per Level
config.ebwizardry.siphon_mana_per_level.tooltip=The amount of mana given for a kill for each level of siphon upgrade.
config.ebwizardry.condenser_tick_interval=Condenser Tick Interval
config.ebwizardry.condenser_tick_interval.tooltip=The number of ticks between each mana increase for wands with the condenser upgrade.
config.ebwizardry.base_spell_slots=Base Spell Slots
config.ebwizardry.base_spell_slots.tooltip=The number of spell slots a wand has with no attunement upgrades applied.
config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament
config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Disable to prevent stealing of items.
config.ebwizardry.telekinetic_disarmament.true=Yes - let people steal things
@@ -1575,12 +1592,15 @@ config.ebwizardry.blast_increase_per_level=Blast Increase Per Level
config.ebwizardry.blast_increase_per_level.tooltip=The fraction by which spell blast is increased for each level of blast upgrade. May cause extreme lag with high values!
config.ebwizardry.frost_slowness_increase_per_level=Frost Slowness Increase Per Level
config.ebwizardry.frost_slowness_increase_per_level.tooltip=The fraction by which movement speed is reduced per level of frost effect.
config.ebwizardry.storage_increase_per_level=Storage Increase Per Level
config.ebwizardry.storage_increase_per_level.tooltip=The fraction by which maximum charge is increased for each level of storage upgrade.
config.ebwizardry.category.difficulty=Difficulty Settings
config.ebwizardry.category.difficulty.tooltip=Configure wizardry's difficulty
config.ebwizardry.title.difficulty=Difficulty Settings
config.ebwizardry.subtitle.difficulty=Settings that affect the mod's difficulty.
config.ebwizardry.unfocused_search_bars=Whether to autofocus search bar in Arcane Workbench and lectern
config.ebwizardry.discovery_mode=Discovery Mode
config.ebwizardry.discovery_mode.tooltip=For those who like a sense of mystery! When enabled, spells you haven't cast yet will be unreadable until you cast them (on a per-world basis). Has no effect when in creative mode. Spells of identification will be unobtainable in survival mode if this is disabled.
config.ebwizardry.legacy_wand_levelling=Legacy Wand Levelling
@@ -1601,6 +1621,54 @@ config.ebwizardry.forfeit_chance=Forfeit Chance
config.ebwizardry.forfeit_chance.tooltip=The chance to 'misread' an undiscovered spell and trigger a forfeit instead. Setting this to 0 effectively disables the forfeit mechanic. Has no effect if discovery mode is disabled.
config.ebwizardry.progression_requirements=Progression Requirements
config.ebwizardry.progression_requirements.tooltip=The progression required to upgrade a wand to each tier (apprentice, advanced and master respectively).
config.ebwizardry.tier_max_charges=Tier Maximum Charges
config.ebwizardry.tier_max_charges.tooltip=Maximum mana each tier can store (novice, apprentice, advanced, master respectively).
config.ebwizardry.tier_upgrade_limits=Tier Upgrade Limits
config.ebwizardry.tier_upgrade_limits.tooltip=Maximum number of upgrades each tier can have (novice, apprentice, advanced, master respectively).
config.ebwizardry.novice_max_charge=Novice Maximum Charge
config.ebwizardry.novice_max_charge.tooltip=Maximum mana a novice wand can store.
config.ebwizardry.apprentice_max_charge=Apprentice Maximum Charge
config.ebwizardry.apprentice_max_charge.tooltip=Maximum mana an apprentice wand can store.
config.ebwizardry.advanced_max_charge=Advanced Maximum Charge
config.ebwizardry.advanced_max_charge.tooltip=Maximum mana an advanced wand can store.
config.ebwizardry.master_max_charge=Master Maximum Charge
config.ebwizardry.master_max_charge.tooltip=Maximum mana a master wand can store.
config.ebwizardry.novice_upgrade_limit=Novice Upgrade Limit
config.ebwizardry.novice_upgrade_limit.tooltip=Maximum number of upgrades a novice wand can have.
config.ebwizardry.apprentice_upgrade_limit=Apprentice Upgrade Limit
config.ebwizardry.apprentice_upgrade_limit.tooltip=Maximum number of upgrades an apprentice wand can have.
config.ebwizardry.advanced_upgrade_limit=Advanced Upgrade Limit
config.ebwizardry.advanced_upgrade_limit.tooltip=Maximum number of upgrades an advanced wand can have.
config.ebwizardry.master_upgrade_limit=Master Upgrade Limit
config.ebwizardry.master_upgrade_limit.tooltip=Maximum number of upgrades a master wand can have.
config.ebwizardry.condenser_amount_multiplier=Condenser Amount Multiplier
config.ebwizardry.condenser_amount_multiplier.tooltip=Multiplier for condenser upgrade mana regeneration amount. Higher values make condensers more effective.
config.ebwizardry.flesh_spells_cause_slowness=Flesh Spells Cause Slowness
config.ebwizardry.flesh_spells_cause_slowness.tooltip=Whether flesh spells (DiamondFlesh, IronFlesh, OakFlesh) apply slowness. When disabled, these spells only provide their defensive benefits without movement penalty.
config.ebwizardry.flesh_spells_cause_slowness.true=Yes - flesh spells slow you down
config.ebwizardry.flesh_spells_cause_slowness.false=No - flesh spells don't slow you down
config.ebwizardry.diamond_flesh_armor_bonus=DiamondFlesh Armor Bonus
config.ebwizardry.diamond_flesh_armor_bonus.tooltip=Armor bonus provided by the DiamondFlesh spell.
config.ebwizardry.diamond_flesh_armor_toughness_bonus=DiamondFlesh Armor Toughness Bonus
config.ebwizardry.diamond_flesh_armor_toughness_bonus.tooltip=Armor toughness bonus provided by the DiamondFlesh spell.
config.ebwizardry.iron_flesh_armor_bonus=IronFlesh Armor Bonus
config.ebwizardry.iron_flesh_armor_bonus.tooltip=Armor bonus provided by the IronFlesh spell.
config.ebwizardry.oak_flesh_armor_bonus=OakFlesh Armor Bonus
config.ebwizardry.oak_flesh_armor_bonus.tooltip=Armor bonus provided by the OakFlesh spell.
config.ebwizardry.oak_flesh_health_bonus=OakFlesh Health Bonus
config.ebwizardry.oak_flesh_health_bonus.tooltip=Health bonus provided by the OakFlesh spell (as a multiplier, e.g., 0.2 = 20% increase).
config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor
config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1.
config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor
@@ -1625,6 +1693,10 @@ config.ebwizardry.replace_vanilla_fall_damage=Replace Vanilla Fall Damage
config.ebwizardry.replace_vanilla_fall_damage.tooltip=Whether to replace Minecraft's distance-based fall damage calculation with an equivalent, velocity-based one. This is done such that mobs in freefall will take exactly the same damage as normal, so it will not break falling-based mob farms. Disable this if you experience falling-related weirdness! If this is disabled, some spells revert to a more simplistic method of resetting the player's fall damage in certain cases.
config.ebwizardry.replace_vanilla_fall_damage.true=Yes - it's parkour time
config.ebwizardry.replace_vanilla_fall_damage.false=No - break my legs normally
config.ebwizardry.spell_book_colors_require_charm=Spell Book Colors Require Archivist's Eyeglass
config.ebwizardry.spell_book_colors_require_charm.tooltip=Whether to require the Archivist's Eyeglass charm artifact to see colored spell book bookmarks.
config.ebwizardry.spell_book_colors_require_charm.true=Yes - I want to see the colours only if the charm is worn
config.ebwizardry.spell_book_colors_require_charm.false=No - I want to see the colours all the time
config.ebwizardry.blindness_tweak=Blindness Tweak
config.ebwizardry.blindness_tweak.tooltip=Whether to tweak the blindness effect to reduce follow distance when used on non-players. This automatically disables itself in favour of Potion Core's implementation if installed.
config.ebwizardry.mob_loot_table_whitelist=Mob Loot Table Whitelist
@@ -1784,6 +1856,8 @@ config.ebwizardry.mobs_immune_to_ice=Mobs Immune To Ice
config.ebwizardry.mobs_immune_to_ice.tooltip=List of names of entities that are immune to ice, in addition to the defaults. Add mod creatures to this list if you want them to be immune to ice magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_lightning=Mobs Immune To Lightning
config.ebwizardry.mobs_immune_to_lightning.tooltip=List of names of entities that are immune to lightning, in addition to the defaults. Add mod creatures to this list if you want them to be immune to lightning magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_magic=Mobs Immune To Magic
config.ebwizardry.mobs_immune_to_magic.tooltip=List of names of entities that are immune to magic, in addition to the defaults. Add mod creatures to this list if you want them to be immune to magic damage and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_wither=Mobs Immune To Wither
config.ebwizardry.mobs_immune_to_wither.tooltip=List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry\:wizard).
config.ebwizardry.mobs_immune_to_poison=Mobs Immune To Poison
@@ -1829,3 +1903,5 @@ spell.ebwizardry\:invigorating_presence_festive=Invigorating Presents
spell.ebwizardry\:empowering_presence_festive=Empowering Presents
wizard.debug=%1$s, %2$s, %3$s
wizardry.shrine_already_looted=You have already looted this shrine!
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,14 +1,14 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book"
},
"parent": "ebwizardry:item/spell_book_undiscovered",
"overrides": [
{
"predicate": {
"festive": 1
},
"model": "ebwizardry:item/festive_spell_book"
}
{"predicate": {"ebwizardry:element": 1}, "model": "ebwizardry:item/spell_book_discovered_magic"},
{"predicate": {"ebwizardry:element": 2}, "model": "ebwizardry:item/spell_book_discovered_fire"},
{"predicate": {"ebwizardry:element": 3}, "model": "ebwizardry:item/spell_book_discovered_ice"},
{"predicate": {"ebwizardry:element": 4}, "model": "ebwizardry:item/spell_book_discovered_lightning"},
{"predicate": {"ebwizardry:element": 5}, "model": "ebwizardry:item/spell_book_discovered_necromancy"},
{"predicate": {"ebwizardry:element": 6}, "model": "ebwizardry:item/spell_book_discovered_earth"},
{"predicate": {"ebwizardry:element": 7}, "model": "ebwizardry:item/spell_book_discovered_sorcery"},
{"predicate": {"ebwizardry:element": 8}, "model": "ebwizardry:item/spell_book_discovered_healing"},
{"predicate": {"festive": 1}, "model": "ebwizardry:item/festive_spell_book"}
]
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_earth"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_fire"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_healing"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_ice"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_lightning"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_magic"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_necromancy"
}
}
@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book",
"layer1": "ebwizardry:items/spell_book_overlay_sorcery"
}
}
@@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "ebwizardry:items/spell_book"
}
}
@@ -866,4 +866,5 @@
]
}
}
}
}
@@ -12,7 +12,7 @@
"images": {
"workbench": {
"location": "ebwizardry:textures/gui/handbook/pictures/arcane_workbench.png",
"caption": "Мистический верстак",
"caption": "Верстак для тайноведения",
"u": 0,
"v": 0,
"width": 110,
@@ -28,7 +28,7 @@
},
"magic_crystal": {
"location": "ebwizardry:textures/items/crystal_magic.png",
"caption": "Волшебный кристалл",
"caption": "Магический кристалл",
"u": 6,
"v": 6,
"width": 36,
@@ -39,7 +39,7 @@
},
"magic_wand": {
"location": "ebwizardry:textures/items/wand_novice.png",
"caption": "Волшебный жезл",
"caption": "Магический жезл",
"u": 0,
"v": 0,
"width": 64,
@@ -48,7 +48,7 @@
},
"crystal_flower": {
"location": "ebwizardry:textures/gui/handbook/pictures/crystal_flower.png",
"caption": "Кристальный цветок",
"caption": "Хрустальный цветок",
"u": 0,
"v": 0,
"width": 48,
@@ -56,7 +56,7 @@
},
"wizard_tower": {
"location": "ebwizardry:textures/gui/handbook/pictures/wizard_tower.png",
"caption": "Башня волшебника",
"caption": "Башня колдуна",
"u": 0,
"v": 0,
"width": 110,
@@ -64,7 +64,7 @@
},
"wizard_armour": {
"location": "ebwizardry:textures/gui/handbook/pictures/wizard_armour.png",
"caption": "Набор брони Волшебника",
"caption": "Комплект брони колдуна",
"u": 0,
"v": 0,
"width": 90,
@@ -72,7 +72,7 @@
},
"classes": {
"location": "ebwizardry:textures/gui/handbook/pictures/classes.png",
"caption": "3 Класса",
"caption": "3 класса",
"u": 0,
"v": 0,
"width": 96,
@@ -89,7 +89,7 @@
},
"shrine": {
"location": "ebwizardry:textures/gui/handbook/pictures/shrine.png",
"caption": "Святилище земли",
"caption": "Храм земли",
"u": 0,
"v": 0,
"width": 110,
@@ -97,7 +97,7 @@
},
"library_ruins": {
"location": "ebwizardry:textures/gui/handbook/pictures/library_ruins.png",
"caption": "Разрушенные библиотеки",
"caption": "Руины библиотеки",
"u": 0,
"v": 0,
"width": 110,
@@ -122,7 +122,7 @@
},
"elements": {
"location": "ebwizardry:textures/gui/handbook/pictures/elements.png",
"caption": "7 мистических аспектов",
"caption": "7 мистических стихий",
"u": 0,
"v": 0,
"width": 106,
@@ -294,9 +294,9 @@
},
"text": [
"Руководство волшебника",
"Руководство колдуна",
"от Electroblob"
"автор: Electroblob"
]
},
@@ -304,11 +304,11 @@
"title": "Введение",
"text": [
"Приветствую тебя, Волшебник! Эта книга объясняет множество путей мистики и как их использовать. Эта не обычная книга, тем не менее - её страницы будут осуществляться перед Вами, когда Вы узнаете больше о Волшебном мире.",
"Приветствую, колдун! Эта книга описывает множество порядков тайноведения и как их применять. Это не простая книга, правда — её страницы будут материализоваться перед тем, как вы будете больше узнавать о магическом мире.",
"Используйте кнопки Стрелок, чтобы переключаться между страницами, и кнопки с двойными стрелками, чтобы быстро перелистываться между разделами. Используйте центральную кнопку Меню, чтобы вернуться на основную страницу содержимого.",
"Используйте кнопки со стрелками для переворота страницы, а кнопки с двойными стрелками для быстрого переключения между разделами. Используйте центральную кнопку меню для возврата на главную страницу.",
"Нажмите на любую фиолетовую текстовую ссылку, чтобы перейти сразу на соответствующую страницу. Нажмите Пкм по закладке, чтобы сдвинуть её на текущую страницу, и нажмите лкм, чтобы вернуться к странице с закладкой."
"Нажмите на пурпурный текст ссылки, чтобы сразу перейти на соответствующую страницу. Нажмите ПКМ на закладке, чтобы перейти на текущую страницу, а ЛКМ для возврата на страницу, отмеченную закладкой."
]
},
@@ -323,25 +323,25 @@
},
"setting_up": {
"title": "Создание",
"title": "Подготовка",
"include_in_contents": "main_contents",
"text": [
"Чтобы начать осваиваться в моём моде Wizardry, Вам понадобится:",
"Чтобы начать wizardry, вам понадобится:",
"- Волшебный жезл, создаётся как это показано:",
"— Магический жезл, создаваемый, как показано на рисунке:",
"#recipe magic_wand",
"- @arcane_workbench Мистический верстак@, создаётся как это показано:",
" @arcane_workbench Верстак для тайноведения@, создаваемый таким образом: ",
"#recipe arcane_workbench",
"- @spells Книга заклинаний@. Их можно найти в сундуках, выпавшие как добыча, купленные у Волшебников, или Вы можете сделать Заклинание новичка: Волшебный метательный снаряд, как это показано:",
" @spells Колдовская@ книга. Они могут быть найдены в сундуках, выпадаемые как добыча, купленные у колдунов или вы можете сделать заклинание новичка «Магический снаряд», как показано на рисунке:",
"#recipe magic_missile_spell_book",
"Вам также нужно куча других Волшебных кристаллов, чтобы снабдить свой Жезл @mana маной@."
"Вам ещё понадобится куча магических кристаллов, чтобы снабжать свой жезл @mana маной@."
]
},
@@ -353,13 +353,13 @@
],
"text": [
"Мана - мистическая энергия, которая даёт Волшебникам их силы. Сама по себе она не физическая форма; скорее всего, она всегда присутствует в Ауре, которая проникает во всё в мире. В частности, она проявляется в Кристаллах, встречающихся в природе, которые могут быть найдены вложенными в камнях под землёй. @wands Жезлы@ могут хранить в себе Ману, и эта мана направляется в @spells Заклинания@, которых Вы можете бросить. Некоторые заклинания требуют некое количество маны, в зависимости от того, насколько они сильны и как долго они длятся.",
"Мана — эзотерическая энергия, дарующая колдунам их силы. Мана сама по себе не материальное вещество, скорее вездесущая аура, пронизывающая вообще всё. По сути она проявляется в самих кристаллах, что появляются естественным образом и которых можно найти под землёй, погружённых в камни. @wands Жезлы@ могут хранить в себе ману, а эта мана направляется в используемые вами @spells заклинания@. Различные заклинания требуют различное количество маны в зависимости от того, насколько они мощные и как долго они держатся.",
"#image crystal_ore",
"#image magic_crystal",
"Общепризнано, что Ману нельзя создать или уничтожить, и когда Заклинание брошено, ману, которую оно направляет, просто рассеивает в окрестностях. Безусловно, именно таким образом и существует большая часть маны - рассеянной по всему миру. Однако, чтобы мана была полезной, она может быть сконцентрирована, либо естественным путём на протяжении тысячелетий; как в случае с кристаллами под землёй, или искусственно - продвинутая тема, которая не предусмотрена в этой книге. Кроме того, существуют различные способы создания более эффективных Заклинаний и восстановить часть маны, рассеянную во время наложения Заклинаний."
"Общепризнанный факт, что ману невозможно создать или уничтожить, и что, когда используется заклинание, направленная мана буквально рассеивается в окружающий мир. Безусловно, вот как существует большая часть маны, понемногу усеяна по всему миру. Однако, чтобы мана была полезной, она должна быть концентрированной либо естественным образом на протяжении тысячелетий, аналогично кристаллам под землёй, или искусственно продвинутая тема, не описывающаяся в этой книге. Кроме того, существуют различные пути, чтобы сделать заклинания более мана-экономичными и восстановить немного усеянной маны во время прочтения заклинания."
]
},
@@ -371,17 +371,17 @@
],
"text": [
"Жезл, это орудие выбора Волшебника. С ним Вы можете бросить любое @spells Заклинание@, если Жезл может сдерживать его силу (смотрите: @tiers Уровни@). Жезлы бывают разных видов, но Вы скорее всего приступите с простого Волшебного жезла.",
"Жезлы — выбор каждого колдуна. С его помощью вы сможете использовать любые @spells заклинания@, при условии, что жезл может удерживать его силу (см. @tiers Уровни@). Жезлы существуют во множество различных вариаций, но вы почти наверняка начнёте с базового жезла.",
"#image magic_wand",
"Большинство жезлов начинают свою жизнь как простое устройство своего рода Волшебного кристалла, прилагаемый к деревянной палке, с золотым самородком прикреплённый на другом конце. Кристалл это, разумеется, источник силы Жезла, поскольку он обеспечивает фокусировку, необходимая для направления @spells Заклинаний@. Оставшаяся часть Жезла тоже очень важна, поскольку она обеспечивает не только средства, с которыми держится Жезл, но также траектория, через которую @mana Ману@ можно направить и ориентировать.",
"Большинство жезлов начинают свою жизнь устройством из нечто вроде магического кристалла, прикреплённого к деревянной палке с кусочком золота, закреплённым на другом конце. Разумеется, кристалл — источник силы жезла, он обеспечивает необходимую фокусировку для направления @spells заклинаний@. Всё же, оставшаяся часть жезла тоже важна — она не только снабжает средствами, с помощью которых держится жезл, но также путь, с помощью которого можно направлять и управлять @mana маной@.",
"Чем больше заклинаний брошено Жезлом, тем больше он становится эффективным в направлении @spells Заклинаний@, и следовательно, может бросить заклинания большей силы. Этот эффект может быть различим по едва различимым изменениям, которые возникают в форме и внешнем виде самого Жезла: поскольку Жезл становится всё более мощным, его кристалл будет становиться всё более ярким, его цвет может изменяться в зависимости от его @elements Аспекта@, и спустя определённое время, дерево из которого он сделан, начнёт расти и меняться в более сложные формы.",
"Чем больше жезл использует заклинаний, тем более эффективным он становится при направлении @spells заклинаний@, и тем самым сможет использовать заклинания большей силы. Этот эффект можно различить по едва заметным изменениям, проявляющимся по форме и внешнему виду самого жезла: по мере того как жезл становится более мощным, кристалл становится более выразительным, а цвет может меняться в зависимости от его @elements стихии@, и спустя какое-то время древесина, из которой он сделан, начнёт расти и превращаться в более сложные формы.",
"Когда держите Жезл, будет показан небольшой предупреждающий дисплей в углу экрана. Это показывает Заклинание, которое в данный момент выбрано совместно с графическим изображением, и, если Заклинание было брошено, панель отката укажет время до того, когда Заклинание снова можно бросить. Он также показывает название следующих и предыдущих Заклинаний связанных с Жезлом. Чтобы переключаться между Заклинаниями, используйте клавиши #next_spell_key и #previous_spell_key (их можно поменять в Настройках -> Управление). Также Вы можете переключать Заклинания за счёт прокручивания колёсика мыши при крадении.",
"При удерживании жезла в углу экрана появится небольшой индикатор. Этот индикатор отображает выбранное текущее заклинание вместе с рисунком заклинания, и всякий раз, когда используется заклинание, полоска перезарядки отсчитывает время, после которого можно повторно использовать заклинание. Кроме того, он отображает названия следующих и предыдущих заклинаний, связанных с жезлом. Чтобы переключать заклинания, используйте клавишу #next_spell_key и #previous_spell_key (их можно изменить в настройки —> управление). Кроме того, вы можете переключать заклинания с помощью колёсика мыши во время крадения.",
"При просмотре инвентаря, наведя курсор на Жезл отобразит, сколько хранится в нём @mana Маны@, вместе с его текущим выбранным @spells Заклинанием@ и любыми особенными способностями, с которыми он может обладать. Больше детальной информации о Жезле можно посмотреть, положив его в @arcane_workbench Мистический верстак@."
"При просмотре инвентаря, наведение курсора на жезл покажет, сколько в нём хранится маны вместе с его текущим выбранным @spells заклинанием@ и какими-либо определёнными способностями, что есть у него в наличии. Больше подробной информации касательно жезла можно просмотреть, поместив жезл в @arcane_workbench верстак для тайноведения@."
]
},
@@ -393,20 +393,20 @@
],
"text": [
"@wands Жезл@ бесполезен без Заклинаний, чтобы бросать с его помощью. Заклинания записаны в двух формах: в книгах, которые являются постоянными, и в свитках, которые являются временными.",
"@wands Жезл@ без заклинаний бесполезен. Заклинания записываются двумя формами: в книги, которые постоянные, и в свитки, которые временные.",
"Книги с заклинаниями сами по себе малополезные; вместо этого они используются для привязывания Заклинания, которые они содержатся в @wands Жезле@. Книги с заклинаниями можно найти во всём мире и Вам не потребуется на это много времени. Если Вы нашли одну, то Вы можете навести мышкой на Книгу с заклинанием, чтобы сразу посмотреть основную информацию о Заклинании. Вы также можете нажать Пкм, пока держите Книгу с заклинанием, чтобы узнать о ней подробнее.",
"Колдовские книги в одиночку малопригодны, на самом деле они используются для привязки заклинания и содержатся в @wands жезле@. Колдовские книги можно найти по всему миру, это не займёт много времени. Когда вы её нашли, наведите курсором на колдовскую книгу, чтобы сразу посмотреть основную информацию о заклинании. Кроме того, нажмите правую кнопку мыши во время удерживания колдовской книги, чтобы прочесть её описание",
"Свитки, со другой стороны, служат для другой цели: Щёлкнув пкм, пока держите Свиток, бросит Заклинание которое связано с ним, уничтожая Свиток в процессе. Подобно книгам с заклинаниями, Свитки можно найти во всём мире, но Вы также можете делать их сами (смотрите: @enchanting_scrolls Зачаровывание свитков@)",
"Свитки же служат другой цели: нажатие ПКМ во время удерживания прочтёт связанное заклинание, уничтожив свиток в процессе. Подобно колдовским книгам, свитки можно найти по всему миру, но вы можете сделать их самому (см. @enchanting_scrolls Зачарование свитков@).",
"Свитки весьма полезны, если Вам нужно использовать Заклинание один раз или дважды по конкретному заданию, чтобы не занимать место в слоте в своём @wands Жезле@. У них также имеется преимущество, мгновенно бросать Заклинания, которые обычно требуют период зарядки. Следует подчеркнуть, они являются неэффективным методом бросания Заклинаний по сравнению с использованием Жезла.",
"Свитки очень полезны, если вам нужно использовать заклинание несколько раз в рамках индивидуального задания, и им не нужно занимать слот на вашем @wands жезле@. Кроме того, у них есть преимущество — возможность мгновенно использовать заклинания, что обычно не требует период зарядки. Следует отметить, что они не являются эффективным способом использования заклинания по сравнению с использованием жезла.",
"На первый взгляд, волшебные руны с помощью которых написаны Заклинания, нечитаемы. Для определения Заклинания, Вы можете, несомненно, бросить Заклинание и посмотреть, что произойдёт - хотя, это может вызвать нежеланные побочные последствия, и большинство Заклинаний нуждаются в определённых условиях для работы. Более того, всегда есть вероятность недооценить неизвестное Заклинание когда бросаете, с потенциально опасными последствиями в зависимости от того, насколько мощное Заклинание. Более безопасный и более надёжный вариант - использовать Свиток идентификации, который можно найти в сундуках или купить у Волшебника. Щёлкните Пкм, пока держите, Свиток идентифицирует первую неизвестную Книгу с заклинанием или Свиток в панели быстрого доступа, потребляя Свиток идентификации в процессе."
"Для необученного глаза магические руны, на которых написаны заклинания, неразборчивые. Для того чтобы опознать заклинание, вы непременно можете использовать заклинание и посмотреть, что произойдёт, правда, такая практика может вызвать нежелательные побочные эффекты, и большинство заклинаний требуют определённых условий, чтобы работать. Кроме того, при прочтении всегда есть шанс неправильно прочесть неизвестное заклинание с потенциально опасными последствиями независимо от того, насколько мощное заклинание. Безопасный и надёжный возможный вариант использовать свиток опознания, найденный в сундуках или купленный у колдуна. Нажатие ПКМ во время удерживания опознаёт первую неизвестную колдовскую книгу или заклинание в горячей панели, между делом расходуя свиток опознания."
]
},
"arcane_workbench": {
"title": "Мистический верстак",
"title": "Верстак для тайноведения",
"include_in_contents": "main_contents",
"contents": {
"id": "arcane_workbench_subsections",
@@ -419,11 +419,11 @@
],
"text": [
"Мистический верстак - блок, похожий по внешнему виду на Стол зачарования, где Вы можете зарядить, улучшить и привязать @spells Заклинания@ в свой @wands Жезл@. Его можно создать следующим образом:",
"Верстак для тайноведения — блок, схожий внешним видом на чародейский стол, где можно заряжать, улучшать и привязывать @spells заклинания@ к своему @wands жезлу@. Его можно создать как следует ниже:",
"#recipe arcane_workbench",
"Следующие страницы объясняют различные действия, которые могут быть выполнены в Мистическом верстаке.",
"Следующие страницы объясняют разные действия, которые могут осуществляться в «Верстаке для тайноведения».",
"#image workbench"
],
@@ -436,7 +436,7 @@
],
"text": [
"Чтобы привязать @spells Заклинание@ к своему @wands Жезлу@, просто поместите свой Жезл в центральный слот верстака, затем поместите Книгу с заклинанием в одно из окружающих слотов и нажмите Применить. Обратите внимание, что Книга с заклинанием остаётся нетронутой во время этого процесса - это потому, что акт привязки Заклинания не 'берёт' Заклинание из Книги с заклинанием; напротив, она поднастраивает Жезл к Заклинанию. По существу, Вы можете изменять Заклинания привязанные к Жезлу, столько, сколько пожелаете, просто повторив процесс привязывания Заклинания. Вы можете привязать до 5 Заклинаний к Жезлу за раз, хотя, это число может быть увеличено с Улучшением: Сонастройка."
"Для привязки @spells заклинания@ к @wands жезлу@, поместите жезл в центральный слот верстака, затем поместите колдовскую книгу в один из окружающих слотов и нажмите «Применить». Вы заметите, что колдовская книга останется нетронутой во время этого процесса. Это обусловлено тем, что акт привязки заклинания не 'забирает' заклинание из колдовской книги, вернее, она настраивает жезл на заклинание. По сути, вы можете заменить заклинания, что связаны с вашим жезлом, по желанию, буквально повторив процесс привязки заклинания. Вы можете связать до пяти заклинаний одновременно, тем не менее это число может быть увеличено «Улучшениями для жезла: Настройка»."
]
},
"charging_wands": {
@@ -447,7 +447,7 @@
],
"text": [
"Чтобы зарядить свой @wands Жезл@, поместите Жезл в центральный слот Мистического верстака и поместите несколько волшебных кристаллов в нижний левый слот, и затем нажмите Применить. Каждый кристалл стоит #mana_per_crystal @mana Маны@, и Жезл возьмёт столько, сколько ему необходимо, поэтому, Вы можете сохранить запасы кристаллов в Верстаке, когда они Вам понадобятся. Однако, имейте ввиду, что Жезл может взять только целое количество кристаллов, так что, если Жезлу необходимо, например, 30 и более маны, #example_charging_loss маны будет потеряно при зарядки."
"Чтобы зарядить @wands жезл@, поместите его в центральный слот верстака для тайноведения и немного магических кристаллов в левый нижний слот, а затем нажмите «Применить». Сумма каждого кристалла #mana_per_crystal @mana маны@, но жезл заберёт столько, сколько он потребует, а значит, вы можете сохранить запас кристаллов в верстаке на случай, когда они вам потребуются. Впрочем, зарубите себе на носу: жезл забирает только целое число кристаллов. Если жезлу потребуется, напр.: более 30 маны, то при зарядке жезла израсходуется #example_charging_loss маны."
]
},
"upgrading_wands": {
@@ -458,42 +458,42 @@
],
"text": [
"Чтобы улучшить свой @wands Жезл@, Вам нужен Фолиант арканы подходящего @tiers Уровня@ или специальное улучшение Жезла. Обе, могут быть найдены как трофей или куплены у Волшебника. Чтобы улучшить свой Жезл, поместите его в центральный слот для Улучшения в правом верхнем слоте, затем нажмите Применить.",
"Чтобы улучшить @wands жезл@, вам потребуется фолиант чар соответствующего @tiers уровня@ или специальное улучшение для жезла. Оба из них можно найти в качестве добычи или куплены у колдуна. Чтобы улучшить жезл, поместите его в центральный слот для улучшения в правом верхнем слоте, затем нажмите «Применить».",
"Специальные улучшения Жезла, повышают конкретный аспект Жезла. Каждый тип Улучшения можно складывать до 3х раз, и общее число улучшений, которых Жезл может принимать, зависит от его уровня. Улучшения нельзя удалить."
"Специальные улучшения улучшают особый аспект жезла. Каждый тип улучшения можно суммировать до 3-х раз, а общее количество улучшений, что может взять на себя жезл, зависит от его уровня. Улучшения невозможно убрать."
]
},
"enchanting_scrolls": {
"title": "Зачаровывание свитков",
"title": "Зачарование свитков",
"include_in_contents": "arcane_workbench_subsections",
"triggers": [
"ebwizardry:handbook/scrolls"
],
"text": [
"Мистический верстак также может быть использован для Зачаровывания @spells заклинаний@ и Свитков. Для этого, Вам понадобится Пустой свиток, созданный из кусочка бумаги и несколько ниток, Книга с заклинанием выбранная для Заклинания, и достаточно волшебных кристаллов, чтобы предоставить @mana Ману@, для наложения.",
"Для зачаровывания @spells заклинаний@ и свитков используйте «Верстак для тайноведения». Вам потребуется: чистый свиток, созданный из кусочка бумаги, нить, колдовская книга с выбранным заклинанием и достаточно количество магических кристаллов для обеспечения @mana маны@, чтобы наложить заклинание или свиток.",
"#recipe blank_scroll",
"Поместите кристаллы в нижний левый слот, Пустой свиток в центральный слот, а Книгу с заклинанием в отдельный слот над ним, затем нажмите Подтвердить, чтобы зачаровать Свиток."
"Поместите кристаллы в левый нижний слот, чистый свиток в центральный слот, а колдовскую книгу в отдельный слот над ним, затем нажмите «Подтвердить», чтобы зачаровать свиток."
]
}
}
},
"wizard_armour": {
"title": "Броня волшебника",
"title": "Броня колдуна",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:arcane_initiate"
],
"text": [
"Как волшебник, Вам нужно что-то, чтобы защитить Вас от существ, с которыми Вы сражаетесь. Впрочем, простая броня не подойдёт. Чтобы наиболее эффективно использовать свои Заклинания, Вам потребуется Броня волшебника: Шляпа, роба, поножи и ботинки. В отличие от простой брони, эти не ломаются. Вместо этого, они используют мистическую энергию для защиты Носителя. Это, безусловно, означает, что они могут быть заряжены Волшебными кристаллами, точно также как Жезлы. К счастью, эта одежда не потребляет много @mana Маны@. Зарядите их, как Жезл в @arcane_workbench Мистическом верстаке@ или @mana_flasks Фляжками с маной@, но будьте осторожны - если у них кончится мана, то Вы окажетесь беззащитным.",
"Будучи колдуном, для сражения с существами, вам потребуется что-то, что защитит вас от них. Всё же обычная броня не сгодится. Чтобы создавать большую часть заклинаний, вам потребуется «Броня колдуна»: шляпа, мантия, поножи и сапоги. В отличие от обычной брони, эти не разрушаются. Напротив, они используют сакральную энергию, чтобы оберегать носителя. Разумеется, это означает, что их нужно заряжать магическими кристаллами, как и жезлы. К счастью, это одеяние не тратит много @mana маны@. Заряжайте их, как бы вы это делали с жезлом, в @arcane_workbench верстаке для тайноведения@ или при помощи @mana_flasks колб маны@, но будьте осмотрительны, если у них закончится мана, вы будете уязвимы.",
"#image wizard_armour",
"Вы можете получить броню Волшебника создав её из Волшебного шёлка:",
"Вы можете получить «Броню колдуна», создав её из натурального магического шёлка:",
"#recipe magic_silk",
"#recipe wizard_hat",
@@ -501,30 +501,30 @@
"#recipe wizard_leggings",
"#recipe wizard_boots",
"Можно зачаровать Броню волшебника используя обычный Стол зачаровывания, а из-за собственных свойств волшебного шёлка, он довольно эффективен в сдерживании чар. Особенно полезны для Волшебников зачарования защиты - Волшебная, Морозная и Шоковая, которая может оказаться полезной защитой против других обладателей магии.",
"Можно зачаровать Броню колдуна с использованием обычного чародейского стола, а благодаря врождённым свойствам натурального магического шёлка, он обладает свойством эффективно сдерживать чары. Чары: защита от магии, мороза и шока представляют особую пользу колдуну, которые могут пригодиться для защиты от других обладателей магии.",
"Те волшебники, кто посвящают себя практике одной стихии, иногда носят специальную одежду, которая даёт бонусы для этой стихии. Полный набор такой одежды очень востребованный среди Волшебников и её не так просто найти.",
"Колдуны, посвятившие себя практике одной из стихии, иногда носят особое одеяние, которое предоставляет бонусы за счёт стихии. Полный комплект такого одеяния очень популярен среди колдунов, и найти его непросто.",
"Как только они овладеют Основами магии, большинство Волшебников выберут следующий особый путь, либо класс, чтобы продолжить своё обучение. Есть 3 основных класса, к которым Волшебник может присоединиться: Мудрец, Боевой маг и Чародей. Членов каждого класса можно легко отличить по их мантиям, которые модифицируются - обычно с помощью Редких и Экзотических материалов - чтобы лучше удовлетворять свои потребности.",
"Освоив основы магии, большинство колдунов предпочитают следовать особым путём или классу, чтобы продолжить своё обучение. Существует 3 основных класса, к которым колдун может присоединиться: мудрец, боевой маг и чернокнижник. Члены каждого класса могут легко быть различимы по их мантиям, которые обычно модифицируются при помощи редких и экзотических материалов, чтобы лучше соответствовать своими потребностями.",
"#image classes",
"Мудрец",
"Посвятив свою жизнь стремлению к знаниям, Мудрец усовершенствовал искусство эффективного бросания заклинания, позволив ему бросать заклинания используя наименьшее возможное количество маны. Их наряд из шёлковой мантии, опровергает значительную защиту, которую они обеспечивают, поскольку Мудрец также является искусным Волшебником - хотя они по-прежнему не могут сравниться с твёрдым металлическим покрытием. Однако, столь длительное проведение периодов времени в безопасной библиотеки делает их весьма вялыми в бою.",
"Посвятив свою жизнь поиску знаний, мудрец достиг совершенства в искусстве эффективного прочтения заклинаний, что позволяет ему читать заклинания, используя наименьшее количество маны из допустимого. Пышный наряд из шёлковых мантий скрывает обеспечиваемую ему прочную защиту. Кроме того, мудрец искусный колдун, правда, наряд по-прежнему и близко не стоит прочной металлической обшивке. Провождение столь длительного периода в безопасной библиотеке в то же время делает его довольно вялым в бою.",
"Боевой маг",
"Боевой маг является приспособленным для боя, одетого в комбинацию из упрочнённой ткани и кристально-серебряного покрытия добываемые на самых квалифицированных плавильных заводах. Через дисциплину и тренировку, боевой маг также улучшил свою скорость и реакции, позволяя ему более часто бросать заклинания. Однако, дёшево это не обходится, причём Боевой маг должен быть осторожен, чтобы слишком быстро не прожигать ману.",
"Боевой маг приспособлен для боя, облачённый в сочетание прочной ткани и хрустальной серебряной обшивки, получаемой из наиболее квалифицированных литейн. Благодаря дисциплине и подготовке, боевой маг усовершенствовал свою скорость и реакцию, что позволяет ему чаще читать заклинания. Это удовольствие не из дешёвых, тем не менее боевой маг должен стараться не прожигать ману слишком быстро.",
ародей",
ернокнижник",
ародей живёт ради исследований, выталкивая границы того, что возможно, чтобы откапывать мистические тайны. Это, совместно со стремлением экспериментировать с тем, что другие могут счесть с запретным, позволили им преодолеть ограничения привычного обучения и бросать заклинания с невероятной скоростью, а также уменьшить использование маны. Их легковесные мантии, хотя и пригодны для разведки, обеспечивают меньшую защиту в бою, чем их представители. "
ернокнижник живёт ради исследований, открывая новые горизонты возможного и извлекая затерянные сакральные тайны. Наряду со склонностью экспериментировать с тем, что другие могут счесть запретным, это позволило ему разрушать путы традиционного обучения и использовать заклинания с огромной скоростью, в том числе снизив себе расход маны. Его лёгкая мантия полезна для исследования, и всё же она оказывает меньшую защиту в бою, чем её аналоги."
]
},
"magical_world": {
"title": "Магия в Мире",
"title": "Магия в мире",
"include_in_contents": "main_contents",
"contents": {
"id": "magical_world_subsections",
@@ -537,42 +537,42 @@
],
"text": [
"Никто не может овладеть мистикой просто сидя в доме - есть целый мир, который полон магии и которую нужно исследовать. Древние руины, реликвии и таинственные особи, хорошие и плохие ждут своего открытия - если знаете, где искать. Величайшие волшебники также самые любопытные, и получают большую часть своих знаний и силы, путём исследования окружающей среды вокруг них и экспериментируя с тем, что они находят.",
"Нельзя овладеть тайнами, просто сидя дома. Откройте для себя мир, полный магией! Древние руины, реликвии и мистические существа добра и зла ждут своего открытия, если знать, где их искать. Величайшие колдуны — самые любопытные, и большую часть своих знаний и силы они получают, исследуя окружающий мир и экспериментируя с тем, что находят.",
"Пока большую часть времени Волшебник неминуемо проводит в одиночестве, обмен знаниями также жизненно важен для изучения мистических искусств. Ищите практикующих специалистов магии, и узнайте от них как можно больше, насколько Вы сможете: общение - пожалуй величайшая сила из всех."
"Несмотря на то, что значительную часть времени колдуны неизбежно проводят в одиночестве, важно обмениваться знаниями для изучения тайноведческих искусств. Ищите практик магии и изучайте столько знаний, сколько сможете себе позволить, — общение, пожалуй, абсолютно величайшая сила."
],
"sections": {
"crystal_flowers": {
"title": "Кристальные цветы",
"title": "Хрустальный цветки",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:handbook/crystal_flowers"
],
"text": [
"В ходе Ваших путешествий, Вы, вероятно, встретите любопытные светящиеся цветки, растущие в дикой природе время от времени. Эти отличительные цветы известны как Кристальные цветы, и они удивительно эффективны в концентрации @mana Маны@ - по сей день, никто не знает почему. Однако, мы точно знаем, что они могут быть собраны и созданы, чтобы извлечь ману как кристаллы, делая их весьма полезным надземным источником маны. Количество маны, получаемое таким способом, ограничено, хотя, за счёт маленьких размеров цветов и благодаря им, растут только в небольших участков.",
"За время ваших путешествий, возможно, вы случайно встретите необычные светящиеся цветы в природе. Эти специфические цветы известны как хрустальные цветки, и они на удивление способны концентрировать @mana ману@. По сей день никто не знает наверняка. По крайней мере, нам известно, что их можно добывать и собирать, чтобы извлекать ману в виде кристаллов, что делает их довольно ценным живым источником маны. Количество маны, получаемое таким образом, ограничено. И всё же, за счёт их малого размера, цветки растут только на небольших участках земли.",
"#image crystal_flower",
"#recipe crystal_flower_to_crystals"
]
},
"wizard_towers": {
"title": "Башни волшебника",
"title": "Башни колдуна",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:wizard_tower"
],
"text": [
"Не страшитесь - Вы не единственный практикующий магии в этом мире. В Ваших путешествиях, Вы вполне можете встретить Высокую башню или 2 с отличительной крышей. Это место проживания товарища Волшебника. Жизнь в уединении иногда может сделать Волшебников немного раздражительными, но они обычно дружелюбные люди, кто желает поделиться своими знаниями с Вами - пусть даже за вознаграждение. Ожидайте, что заплатите ценные металлы и драгоценные камни, а взамен, Вы получите очень много мистического чуда.",
"Вряд ли вы не единственный практик магии в этом мире. В течение своих путешествий вы вполне можете встретить высокую башню или две с отличительной остроконечной крышей. Это обитель колдуна. Порою жизнь в глуши может сделать колдунов малость раздражительными, но в основном они дружелюбный народ и готовы поделиться своими знаниями, хотя и за вознаграждение. Надейтесь расплатиться драгоценными металлами и самоцветами, а в знак благодарности вы получите множество восхитительных эзотерических диковин.",
"С другой стороны, если Вы ищете Заклинание уровня @tiers Мастер@, Вам нужно будет поговорить со специалистом.",
"Если вы искали заклинание @tiers магистра@, вам придётся поговорить с мастером.",
"#image wizard_tower",
"К несчастью, существует всего несколько Волшебников, которые не рады гостям. Эти волшебники чаще всего изгои, и враждебны к любому, кто пересекает их путь. Подходите к этим лицам с предосторожностью, и будьте подготовлены защитить себя против могучей магии. Впрочем, одолейте их, и их знания ждут Вас.",
"К сожалению, в мире существует несколько колдунов, не приветствующие посетителей. Обычно эти колдуны отщепенцы, враждебно настроенные к любому, кто встретится у него на пути. Осторожно приближайтесь к таким индивидам и будьте готовы защитить себя от их мощной магии. Одержите над ними победу, и их знания вам обеспечены.",
"Следует также упомянуть, что ни один Волшебник не потерпит нападению или кражи - и они с готовностью возьмут правосудие в свои руки."
"Кроме того, следует отметить, что ни один колдун не потерпит нападения или воровства, он без промедления учинит самосуд."
]
},
"obelisks": {
@@ -583,76 +583,76 @@
],
"text": [
"Остатки древней магии разбросаны по всему миру, самые примечательные из них - Резные каменные структуры, которые покрыты множеством символов и рун. Эти руины это всё, что по-прежнему осталось от древней цивилизацией. Кто бы, или что бы их ни построило, они очевидно имели значительный объём знаний магии, и использовали её, для размещения защитных чар на таких местах, которые до сих пор существуют по сей день.",
"По миру усеяны остатки древней магии, самые достопримечательные из них — высеченные из камня структуры, выдерживающие множество символов и рун. Эти развалины всё, что осталось от своего рода древней цивилизации. Кто бы ни или что-либо построило их, у них явно было широкое знание магии, и оно применялось для наложения защитных чар на такие места, что существуют по сей день.",
"Эти структуры в целом относятся к двум типам. Первые и более частые из них - Обелиски: высокие шипы резного камня, известный как Рунный камень, с открытой структурой внизу, содержащий мелкие мистические реликвии из забытого прошлого.",
"Структуры, не вдаваясь в детали, относятся к двум типам. Первый и более распространённый среди них обелиски, высокие шипы из высеченного камня, известные как Рунический камень, с решётчатой конструкцией у основания, содержит незначительное количество эзотерических реликвий из забытого прошлого.",
"#image obelisk",
"Эти структуры, как правило, защищены за счёт чар, которые призывают враждебных @creatures Волшебных существ@, если какой-нибудь человек случайно окажется поблизости. Отгоните этих существ и уничтожьте их источник, и реликвии будут Вашими."
"Структуры, как правило, защищены чарами, призывающими враждебных @creatures магических существ@, если вдруг человек окажется слишком близко. Прогоните этих существ и уничтожьте их источник, и тогда реликвии в ваших руках."
]
},
"shrines": {
"title": "Святилища",
"title": "Храмы",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:visit_shrine"
],
"text": [
"Второй, и более редкий тип структуры, известный как Святилище. Эти структуры состоят из круга рунно-каменных столбов, окружающих центральный пьедестал, над которого расположен сундук наполненный древними @artefacts Артефактами@. Этот сундук, как правило, защищён за счёт заклинания Мистическая блокировка, не допуская никого, за исключением Владельца от открытия. Целая структура также защищена за счёт Сдерживающего поля, которое ни кому не допустит уйти, кто случайно окажется слишком близко.",
"Вторая и более редкая, как бы структура, известная как храм. Структуры состоят из круга столбов из рунических камней, окружающих центральный пьедестал, на основании чего расположен сундук, полный древними @artefacts артефактами@. Чаще всего этот сундук защищён чаром «Мистический замок», не позволяя кому-либо открывать его, кроме владельца. Целая структура тоже защищена сдерживающим полем, которое никому не даст сбежать, кто окажется слишком близко.",
"#image shrine",
"Из-за их великой мистической силы и значимости, не говоря уже об сокровища внутри, эти структуры особо привлекательны, для любого начинающего Волшебника - но будьте осторожны. Поступают многочисленные сообщения о Волшебниках попадающих в западню в Сдерживающем поле и постепенно сходящие с ума, возможно, при клаустрофобии. Однако, всё большее число полагают, что такие случаи бывают преднамеренной частью защитной магии Святилища, и что Волшебники попавшие в ловушку внутри Святилища, фактически контролируются для защиты структуры. Крайне негативная возможность, непременно, для любого, кто осмелится рискнуть..."
"Благодаря их великой эзотерической силе и значимости, не говоря уже о богатствах внутри, эти структуры крайне привлекательны для амбициозного колдуна: но проявите осторожность. Существует множество сообщений от колдунов, попавшие в ловушку сдерживающего поля и медленно сходившие с ума, вероятно, из-за клаустрофобии. Хотя всё большее число верят, что такого рода происшествия были умышленной частью защитной магии храма, а колдуны, оказавшиеся в пределах поля, фактически завладеваются для того, чтобы защитить структуру. Пугающая перспектива непременно для любого, кто отважится приблизиться..."
]
},
"library_ruins": {
"title": "Разрушенные библиотеки",
"title": "Руины библиотеки",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:find_library_ruins"
],
"text": [
"Волшебники склонны накапливать довольно много Книг с заклинаниями на протяжении всей своей жизни, и обычно по этой причине, Волшебник строит Библиотеку для хранения своей коллекции. Некоторые особо скрытные Волшебники, были даже известны тем, что хранили свои мистические знания даже под землёй, подальше от любопытных глаз. Тем не менее, библиотека требует ухода, и если его владелец умрёт без своего Подмастерья, который унаследует книги, структура быстро придёт в негодность. Тысячи лет использования магии видели всё больше и больше библиотек, оставленными заброшенными таким путём, и во время исследования, Вы, возможно, наткнётесь на одну из них. Они имеют нередко смысл для поиска любых Книг с заклинаниями или других предметов, которые могли пережить тяжёлые последствия времени.",
"Колдуны чаще всего склонны скапливать довольно много колдовских книг за всю свою жизнь, и в таком случае колдун, как правило, строит себе библиотеку для хранения коллекции. Некоторые весьма таинственные колдуны, как известно, хранят свои тайные знания под землёй, подальше от любопытных глаз. Тем не менее библиотеке нужен уход, но в случае если хозяин умирает в отсутствие подмастерья, книги не перейдут в наследство и вскоре структура придёт в негодность. Тысячелетнее применение магии приводило к тому, что всё больше и больше библиотек забрасывалось таким образом, но, возможно, при исследовании вы могли случайно её встретить. Как правило, они являются ценным источником поиска колдовских книг или других предметов, что смогли пережить разрушительное воздействие времени.",
"#image library_ruins",
"Библиотеки не только используются для хранения книг. Волшебники прошлого, использовали силу Алтаря наполнения, который используется для осуществления наполнения волшебными аспектами на волшебные материалы. Расположение алтаря является точным и сложным исскуством, поскольку, он должен быть соответствовать с учётом небесными движениями и местными колебаниями маны. Их практика строительства стала гораздо более редкой в новейшей истории, в основном, потому, что когда то построенный, твёрдый каменный алтарь держится веками, так что, нет особой обходимости в новых. Вместо этого, большинство Волшебников находят и восстанавливают существующий алтарь на случай того, чтобы необходимо было выполнять ритуалы для наполнения и, - Разрушенные библиотеки идеальное место для поиска.",
"Библиотеки не только используются для хранения книг. В былые времена колдунам доводилось использовать силу алтаря наполнения для выполнения ритуалов, наделяющих стихией магические предметы. Местоположение алтаря точное и сложное искусство, которое должно налаживаться в соответствии с особыми небесными движениями и местными изменениями маны. В современной истории практика их строительства осталась в прошлом, в основном потому что они были построены из твёрдой породы, которая хранится веками, а значит существует малая потребность в новых. На самом деле большинство колдунов находят и чинят тогдашние алтари, если им нужно выполнить ритуалы наполнения, а руины библиотеки идеальное место для поиска.",
"Вы, тоже, можете в определённый момент сочтить это полезным, и построить свою собственную библиотеку, которая, разумеется, потребует несколько Книжных полок:",
"Однажды вы тоже можете посчитать полезным построить собственную библиотеку, которая непременно потребует несколько книжных полок:",
"#recipe bookshelves",
"#recipe gilded_wood",
"Однако, эти книжные полки не просто мебель. Наполняя самые мощные волшебные кристаллы в своих Книжных полках, позволит беспрепятственно передавать знания между ними и близлежащим @arcane_workbench Мистическим верстаком@ - очень удобно, когда коллекция расширяется. В равной степени полезна - Кафедра:",
"Тем не менее эти книжные полки не просто мебель. Наполнение максимально мощного магического кристалла в книжные полки обеспечивает пассивную передачу знаний между ними и близлежащим @arcane_workbench верстаком для тайноведения@, весьма кстати по мере того, как расширяется коллекция. Не менее полезна и кафедра:",
"#recipe lecterns",
"Фолиант, который парит над кафедрой, собирает знания из ближайших Книг с заклинаниями, позволив каждому просматривать их в свободное время, не помня их точного местоположения."
"Книга, парящая над кафедрой, получает знания из близлежащих колдовских книг, что позволяет читать каждое заклинание в свободное время, не запоминая её точного местоположения."
]
},
"creatures": {
"title": "Волшебные существа",
"title": "Магические существа",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:handbook/magical_creatures"
],
"text": [
"Помимо волшебников, множество других существ также используют магию. Многие из этих существ загадочны по своему происхождению, и большую часть можно призвать используя конкретные волшебные @spells Заклинания@. Этих существ можно встретить охраняющих @obelisks Обелиск@, а может и мощного Призывателя. Менее загадочные существа периодически встречаются в глуши.",
"Не считая колдунов, огромное количество разных существ тоже применяют магию. Многие из этих существ имеют эзотерическое происхождение и большую часть можно призвать при помощи определённых магических @spells заклинаний@. Вы можете встретить этих существ, охраняющие @obelisks обелиск@ или, возможно, сильного заклинателя. Второсортные существа редко встречаются в дикой местности.",
"Наглядным примером является Пережиток, наподобие духа, сущность, которая формируется естественным образом из вещества, которое остаётся за счёт мощной магии, и чаще всего, принимает 1 из 7 мистических @elements Аспектов@. По отдельности, они не более чем помеха, но толпа, может быстро ошеломить неподготовленного Искателя приключений, а их присущая устойчивость к магии, требует принятия разумного ношения с собой Лука, или другого оружия дальнего боя.",
"Яркий пример — пережиток по образу и подобию духа, формирующийся естественным образом из следов оставшейся после себя мощной магии. Они, как правило, приобретают один из семи мистических @elements стихий@. Сами по себе они не более чем неприятность, однако рой сможет быстро сокрушить неподкованного авантюриста, а их врождённая устойчивость к магии повод задуматься о ношении лука или другого стрелкового оружия.",
"#image remnant",
"Как только побеждён, с них может быть собрана Спектральная пыль, которая, помимо излучения яркого свечения, имеет ряд полезных свойств. Вместилище, создаётся как показано ниже, оно же, обеспечивает удобное средство её сдерживания.",
"При убийстве с него можно собрать спектральный прах, который, помимо излучения яркого света, обладает рядом полезных свойств. Хранилище (создаётся как показано на рисунке), обеспечивает удобный способ содержания в себе праха.",
"#recipe receptacle",
"Вместилища могут быть размещены на поверхности или прикреплёнными к стене и щёлкнув Пкм спектральной пылью, чтобы заполнить Вместилище. Вместилище действует как мягкий источника света. Нажмите Пкм по заполненному Вместилищу, чтобы опустошить и вернуть пыль."
"Хранилища размещаются на поверхность или крепятся на стену. Нажатие ПКМ спектральным прахом наполняет его, действуя как мягкий источник света. Нажатие ПКМ по заполненному хранилищу опустошает и возвращает прах."
]
},
"artefacts": {
@@ -663,7 +663,7 @@
],
"text": [
"Изучая @shrines Святилища@, возможно, Вам посчастливится обнаружить какой-нибудь древний волшебный артефакт - объект, способный предоставить своему Носителю уникальные и мощные баффы и особые силы. Известны 3 типа, которые были обнаружены: кольца, которые дают бонусы и эффекты к Заклинаниям, амулеты, которые увеличивают защитные свойства, и талисманы, которые дают полезные эффекты. Эти артефакты функционируют только в том случае, если надеть их правильным образом; просто имея их на одном человеке - недостаточно."
"При исследовании @shrines храма@, возможно, вам посчастливилось обнаружить своего рода древние магические артефакты. Предмет способен наделить своего владельца уникальными и мощными усилениями с особыми силами. Было обнаружено 3 типа: кольца, предоставляющие бонусы и эффекты для заклинаний; амулеты, улучшающие способности защиты, а также талисманы, дающие вспомогательные эффекты. Эти артефакты кажется, работают только в том случае, когда надеты должным образом. Носить их при себе недостаточно."
]
}
}
@@ -677,58 +677,58 @@
],
"text":[
"@wands Жезлы@ и @spells Заклинания@ переходят в 4 уровня: #colour_noviceНовичок#colour_reset, #colour_apprenticeподмастерье#colour_reset, #colour_advancedпродвинутый#colour_reset, и #colour_masterмастерский#colour_reset. Каждый уровень намного мощнее, чем предыдущий.",
"@wands Жезлы@ и @spells заклинания @ встречаются четырёх уровнях: #colour_noviceновичок#colour_reset, #colour_apprenticeподмастерье#colour_reset, #colour_advancedпродвинутый#colour_resetand и #colour_masterмагистр#colour_reset. Каждый уровень могущественнее последнего.",
"#image tiers",
"#colour_noviceЖезлы#colour_reset уровня новичок являются теми, кого можно создать. Они хранят до #novice_max_charge маны, и можно наложить только Заклинания уровня новичка. Заклинания новичка достаточно просты, чтобы их мог бросить любой и они обычно не расходуют много маны. Однако, это не обязательно означает, что они бесполезны на высоких уровнях; их низкий расход маны, делает некоторые Заклинания новичка полезными, даже когда у Вас есть доступ к Заклинаниям уровня Мастер.",
"#colour_noviceЖезлы #colour_reset новичка те, что можно создать. Они вмещают до #novice_max_charge ед. маны и могут использовать только заклинания новичка. Заклинания новичка настолько просты, что используются кем-угодно, и они, как правило, не тратят много маны. Однако это не всегда означает, что они бесполезны на следующих уровнях. Их малозатратность делает некоторые начинающие заклинания полезными даже в том случае, когда у вас есть доступ к заклинаниям магистра.",
"#colour_apprenticeЖезлы#colour_reset уровня подмастерья - следующий уровень от Жезлов уровня новичка. Они хранят до #apprentice_max_charge маны и могут бросать Заклинания уровня новичка и подмастерья. Заклинания подмастерья намного сложнее чем Заклинания новичка, но обычно расходуют больше маны.",
"#colour_apprenticeЖезлы #colour_reset подмастерья следующий уровень по сравнению с жезлами новичка. Они вмещают до #apprentice_max_charge ед. маны и могут использовать заклинания уровня новичок и подмастерье. Заклинания подмастерья несколько поразительнее, чем заклинания новичка, но, как правило, тратят гораздо больше.",
"#colour_advancedПродвинутые#colour_reset жезлы - достаточно редки и гораздо сильнее. Они могут бросать все Заклинания помимо Заклинаний уровня Мастер, и хранят до #advanced_max_charge маны. Продвинутые заклинания могут давать супер-человеческие силы такие как: Невидимость и сеять хаос на врагов.",
"#colour_advancedПродвинутые #colour_reset жезлы — та ещё редкость, но в разы мощнее. Они могут использовать всякие заклинания, кроме уровня магистра, и вмещают #advanced_max_charge ед. маны. Продвинутые заклинания наделяют сверхчеловеческими силами, такими как невидимость, и погружают врагов в хаос.",
"#colour_masterМастерские#colour_reset жезлы - более мощные Жезлы в существовании. Они хранят до #master_max_charge маны, и могут бросать любые Заклинания. Книги с заклинаниями уровня Мастер - очень редки и Заклинания могут причинять полные разрушения не только на врагов, но и даже на сам мир. Используйте с большой осторожностью.",
"#colour_masterЖезлы #colour_reset магистра крайне мощные жезлы, существующие в природе. Они вмещают до #master_max_charge ед. маны и могут использовать любое заклинание. Колдовские книги магистра — весьма редкие, причём заклинания могут вызвать полное уничтожение не только врагов, но и самого мира. Используйте с осторожностью.",
"Чтобы улучшить на высокий уровень, Жезл сначала должен стать достаточно мощным, чтобы направлять Заклинания этого уровня. Жезлы получают энергию когда бросают свои Заклинания. Разновидность заклинаний, сила заклинаний, стихийные эффекты и другие внешние факторы могут оказать эффект на то, насколько быстро палочка созревает.",
"Чтобы перейти на следующий уровень, сперва жезл должен стать довольно мощным, чтобы направлять заклинания нового уровня. Жезлы получают силу по мере использования заклинаний. Разновидность заклинания, сила, эффекты стихии и другие внешние факторы могут повлиять на то, как быстро созревает жезл.",
"Как только Жезл достаточно мощный, Фолиант арканы обеспечивает конечный катализатор, необходимый для повышения Жезла на следующий уровень (смотрите @upgrading_wands Улучшение жезлов@), и как только это уже произошло, процесс созревания может начаться заново на следующем уровне."
"Когда жезл достаточно мощный, фолиант чар выполняет роль финального катализатора, необходимого для повышение уровня жезла (см. @upgrading_wands Улучшение жезлов@). Как только это уже произошло, возобновится процесс созревания до следующего уровня."
]
},
"elements": {
"title": "Аспекты/стихии",
"title": "Стихии",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:handbook/elements"
],
"text": [
"@spells Заклинания@ относятся к разным стихиям, которые определяют природу Заклинания, а также дают преимущества, когда используются с разными @wands Жезлами@.",
"@spells Заклинания@ относятся к определённым стихиям, которые определяют природу заклинания, и кроме того, предоставляют преимущество при использовании определённого @wands жезла@.",
"#image elements",
"#colour_fireОгонь#colour_reset \nНаверное, самая разрушительный аспект, огонь относится к горениям, лавам, и взрывам. Могучий пиромант обрушит ад над своих врагов, и возможно, сожжёт мир в процессе. Большинство огненных нападений Заклинанием, поджигают свои цели, постепенно причиняя постоянный урон. Впрочем, будьте осторожны, огненные Заклинания не оказывают воздействий на Незеровских мобов.",
"#colour_fireОгонь#colour_reset \nОчевидно, самая разрушительная стихия — огонь. Она относится к горению, лаве и взрывам. Могущественный пиромант разразит гнев на своих врагов и, вероятно, зажжёт мир по ходу дела. Большая часть огненных заклинаний поджигают свою цель, причиняя ей постоянный урон в течение долгого времени. Тем не менее, будьте бдительны: огненные заклинания не могут действовать на существ Незера.",
"#colour_iceЛёд#colour_reset \nАспект льда, который сделан из всего холодного. Морозные заклинания часто замедляют врагов или замораживают их полностью, и особенно эффективны на огненных существ. Морозная магия может оказаться такой же полезной вне боя, а именно: заморозка воды, чтобы пересечь реку.",
"#colour_iceЛёд#colour_reset \nСтихия льда относится ко всему холодному. Морозные заклинания чаще всего замедляют врагов или полностью их замораживают, а главным образом эффективны по отношению к огненным существам. Магия мороза может оказаться не менее полезной вне боя, напр.: заморозка воды, чтобы пересечь реку.",
"#colour_lightningМолния#colour_reset \nЭтот аспект относится к Молниям, грозам и погодам. Могучий грозовой маг это сила, с которой нужно считаться, а некоторые обладают способностью навлекать молнию по своему желанию. Молниевые заклинания часто повреждают множество врагов сразу, и обычно ищут свою цель, делая их эффективной атакой против любых мобов... но опасайтесь криперов.",
"#colour_lightningГроза#colour_reset \nЭта стихия относится к грозе, штормам и погоде. Могущественный маг грозы — сила, с которой нужно считаться, причём некоторые обладают способностью вызывать грозу, когда они того пожелают. Грозовые заклинания чаще всего наносят вред нескольким врагам одновременно и, как правило, преследуют свою цель, что делает их эффективной атакой против любого существа... Впрочем, остерегайтесь криперов.",
"#colour_necromancyНекроматия#colour_reset \nНекромантия является аспектом тьмы, хаоса и нежити. Некроманты мистические и чаще всего считаются как злыми, хотя обычно это не так. Некромантийные заклинания широко используются для призыва @creatures Существ@, бьющимися за Вас, или даже подчиняют волю своих врагов.",
"#colour_necromancyНекромантия#colour_reset \nНекромантия — элемент тьмы, хаоса и нежити. Некроманты мистические и чаще всего расцениваются как зло, хотя, как правило, это не совсем так. Заклинания некромантии широко применяются для призыва @creatures существ@, чтобы они защищали вас или даже покоряли волю ваших врагов.",
"#colour_earthЗемля#colour_reset \nАспект земли направлен на естественный мир: Животные, растения, ветер, и подобное. Магия земли разнообразная и принимает целый ряд форм, от отравления врагов до высвобождения ярости погоды. Заклинания земли - сочетание атаки, защиты и практичности.",
"#colour_earthЗемля#colour_reset \nСтихия земли основана на естественном существовании: животные, растения, ветер и подобного рода. Магия земли разнородна и принимает самые разнообразные формы, начиная с отравления врагов и заканчивая с высвобождением ярости погоды. Заклинания земли сочетание атаки, защиты и пользы.",
"#colour_sorceryВолшебство#colour_reset \nВолшебство является аспектом силы и разнообразия. Чародеи манипулируют светом, гравитацией и даже самой реальностью, для удовлетворения своих потребностей. Чародейские заклинания можно использовать, среди прочего, чтобы предоставить своему Заклинателю волшебные силы или двигать объекты по своей воле.",
"#colour_sorceryКолдовство#colour_reset \nКолдовство — элемент силы и перемены. Чародеи манипулируют светом, гравитацией, а ещё самой реальностью для удовлетворения своих потребностей. Заклинания колдовства могут использоваться, среди прочего, для наделения колдуна магическими силами или перемещения объектов, когда он того пожелает.",
"#colour_healingЛечение#colour_reset \nАспект лечения соответствует защите и регенерации. Целители стараются защитить себя и своих союзников насколько это возможно, и связи с этим, могут быть весьма сложными соперниками. Хотя в целом, не используется как атака, очищающий свет некоторых исцеляющих Заклинаний, нанесёт немалый урон нежити. Лечебные заклинания незаменимы, когда в бою.",
"#colour_healingИсцеление#colour_reset \nЭлемент исцеления связан с защитой и регенерацией. Знахари стараются защитить себя самих и своих союзников по мере возможности и насколько могут быть опасны соперники. Правда, элемент как правило не используется в качестве атаки. Луч очищения из числа исцеляющих заклинаний нанесёт значительный ущерб нежити. Исцеляющие заклинания незаменимы в бою.",
"Границы между различными стихиями не всегда различаются, а некоторые Заклинания имеют схожие черты аспектов помимо их собственными.",
"Рамки между различными стихиями не всегда чётко выражены, но некоторые заклинания носят черты стихий, не связанные с их собственными.",
"Если Вам повезёт, Вы можете случайно встретить Стихийный кристалл. Стихийные кристаллы - волшебные кристаллы, которые поглощают стихийные свойства по мере своего роста - хотя точные необходимые условия и механизм, с помощью которых это происходит, остаётся загадкой. Нам известно, наверное, что подобные кристаллы можно использовать для создания стихийных Жезлов, которые позволяют бросать Заклинания с нужной стихией с большей мощью, чем обычно."
"В лучшем случае вы можете натолкнуться на стихийный кристалл. Стихийные кристаллы — магические кристаллы, которые поглотили стихийные свойства по мере своего роста. Точные условия и методы обработки, при которых это происходит, остаются «тайной за семью печатями». Однако нам известно, что такие кристаллы могут использоваться для создания стихийных жезлов, которые позволяют применять заклинания соответствующей стихии с большей силой, чем обычно."
]
},
"miscellaneous": {
"title": "Прочее",
"title": "Разное",
"include_in_contents": "main_contents",
"contents": {
"id": "miscellaneous_subsections",
@@ -741,25 +741,25 @@
],
"sections": {
"mana_flasks": {
"title": "Фляжки с маной",
"title": "Колба маны",
"include_in_contents": "miscellaneous_subsections",
"triggers": [
"ebwizardry:handbook/mana_flasks"
],
"text": [
"Arkendur's Arcane Supplies Co. - волшебные предметы для всех Ваших нужд!",
"Arkendur's Arcane Supplies Co. — магические предметы для всяких нужд!",
"Нужно перезарядить свой @wands Жезл@ на ходу? Нет проблем! @mana Ману@ теперь можно разливать в бутылки. Просто изготовьте Фляжку с маной с помощью Колбы и 8 волшебными кристаллами и заберите её с собой. Когда Вам нужно использовать её, просто изготовьте её своим Жезлом и она восстановит немного заряда.*",
"Хотите перезарядить свой @wands жезл@, постоянно находясь в движении? Не проблема! @mana Ману@ теперь можно хранить в бутылках. Создайте колбу маны при наличии бутылочки и восьми магических кристаллов и заберите её с собой. Когда она вам понадобится, внесите её в сетку создания с жезлом, и она восстановит некоторое количество заряда. *",
"#recipe medium_mana_flask",
"НОВОЕ! Внедрение совершенно новых Малых и Больших фляжек с маной, а теперь - Вы можете выбрать размер Фляжки с маной, чтобы удовлетворять свои потребности!",
"НОВИНКА! Представляем совершенно новые колбы маны (малые и большие). Теперь вы можете выбирать размер колбы маны, дабы удовлетворить свои потребности!",
"#recipe small_mana_flask",
"#recipe large_mana_flask",
"* Этот процесс, уничтожит колбу. Определённое количество маны потеряется во время разлива."
"* Действие уничтожит бутылку. При разливе израсходуется некоторое количество маны."
]
},
"throwable_items": {
@@ -770,7 +770,7 @@
],
"text": [
"Различные @spells Заклинания@ наколдовывают физические предметы, некоторых из них также можно создать напрямую. Огненные шары, отравляющие бомбы, искровые бомбы и дымовые бомбы, все из них можно создать:",
"Различные @spells заклинания@ наколдовывают физические предметы, кроме того, некоторые из которых можно создать вручную. Из числа которых: огненные бомбы, ядовитые бомбы, бомбы искр и дымовые бомбы:",
"#recipe firebomb",
"#recipe poison_bomb",
@@ -779,21 +779,21 @@
]
},
"automated_casting": {
"title": "Автоматизированное бросание",
"title": "Автоматическое использование",
"include_in_contents": "miscellaneous_subsections",
"triggers": [
"ebwizardry:enchant_scroll"
],
"text": [
"Последние эксперименты выявили, что возможно автоматизировать бросания @spells Заклинаний@, в какой-то степени, используя исключительно обычный Раздатчик. Никто точно не уверен, почему, но складывается впечатление, что странные свойства Красного камня, даже распространяются на активацию активацию Свитков с заклинаниями. Поместите несколько в Раздатчик и запитайте его, должно сработать ..."
"Недавний эксперимент показал, что возможно автоматизировать использование @spells заклинаний@ при использовании всего лишь простого раздатчика. Никто действительно не знает почему, однако создается впечатление, что странные свойства редстоуна распространяются на активацию свитков. Помещение несколько свитков в раздатчик и запитав его, должно произвести желаемый эффект..."
]
}
}
},
"crafting_recipes": {
"title": "Изготовление рецептов",
"title": "Рецепты создания",
"include_in_contents": "main_contents",
"text": [
@@ -829,37 +829,36 @@
},
"credits": {
"title": "Титры",
"title": "Благодарности",
"include_in_contents": "main_contents",
"text": [
"Electroblob's Wizardry \nВерсия #version \nДля Minecraft #mcversion",
"Разработано, закодировано и затекстурировано: Electroblob",
"Спроектировано, запрограммировано и текстурировано автором: Electroblob.",
"Спасибо Minecraft Forge и MCP, без которого этот мод был бы невозможным.",
"Спасибо Minecraft Forge и MCP, без которого этот мод не был бы осуществим.",
"Спасибо также сообществу Minecraft modding, который всегда имеет решение с моими проблемами в моддинге!",
"Кроме того, спасибо сообществу Minecraft modding, у которого всегда есть решения моих проблем с созданием мода!",
"В дополнении, Я хотел бы поблагодарить следующие лица за их вклад в мод:",
"При этом хотелось бы поблагодарить следующие лица за их вклад в мод:",
"Код:",
"- Corail31 \n- 12foo \n- Shadows-of-Fire \n- HellFirePvP \n- Tora-B \n- Avatair \n- Aeronica \n- UltraHex \n- Azim-Palmer \n- raoulvdberge \n- rafasoares \n- xinyuan-liu \n- SettingDust",
" Corail31; \n- 12foo; \n- Shadows-of-Fire; \n- HellFirePvP; \n- Tora-B; \n- Avatair; \n- Aeronica; \n- UltraHex; \n- Azim-Palmer; \n- raoulvdberge; \n- rafasoares; \n- xinyuan-liu; \n- SettingDust.",
"Переводчики:",
"- Испанский: MadWrist, Alsentar \n- Мексиканский испанский: MadWrist \n- Русский: VilagVil, kellixon, bigenergy, MugGod2, DrHesperus \n- Французский: Hahdrim \n- Бразильский португальский: lorrampi \n- Китайский (Упрощённый): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Корейский: shejery, rewi_wire, 방통, red1854th \n- Польский: Trozuu, Olej \n- Немецкий: BirdyDragon, Lemopav \n- Китайский (Традиционный): chesterccj305 \n- Венгерский: Bombadil",
" Испанский язык: MadWrist, Alsentar; \n- Мексиканский вариант испанского языка: MadWrist; \n- Русский язык: VilagVil, kellixon, bigenergy, MugGod2, DrHesperus & Heimdallr-1; \n- Французский язык: Hahdrim; \n- Бразильский вариант португальского языка: lorrampi; \n- Китайский язык (упрощённый): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer; \n- Корейский язык: shejery, rewi_wire, 방통, red1854th; \n- Польский язык: Trozuu, Olej; \n- Немецкий язык: BirdyDragon, Lemopav; \n- Китайский язык (традиционный): chesterccj305; \n- Венгерский язык: Bombadil.",
"Звуковые эффекты",
"Звуковые спецэффекты:",
"- OhhWowProductions\n- fredzed\n- deleted_user_3277771\n- DiscoveryME\n- OGSoundFX\n- leosalom\n- juskiddink",
" OhhWowProductions; \n- fredzed; \n- deleted_user_3277771; \n- DiscoveryME; \n- OGSoundFX; \n- leosalom; \n- juskiddink.",
"Для большей информации, проверьте нашу @https://github.com/Electroblob77/Wizardry/wiki википедию@.",
"За подробностями, посетите @https://github.com/Electroblob77/Wizardry/wiki википедию@.",
"Не хватает wizardry? Присоединяйтесь к @https://discord.gg/hs8yJP2 Дискорд серверу@ для последних новостей, обсуждений и дополнений!"
"В восторге от Wizardry? Присоединяйтесь к @https://discord.gg/hs8yJP2 Discord-серверу@ ради самых свежих новостей, обсуждений и дополнений!"
]
}
}
}
}
@@ -0,0 +1,865 @@
{
"bookmark_start_section": "introduction",
"colours": {
"text": "#000000",
"caption": "#666666",
"hyperlink": "#601ba0",
"highlight": "#dd4c1d",
"new_section": "#ee88f7"
},
"images": {
"workbench": {
"location": "ebwizardry:textures/gui/handbook/pictures/arcane_workbench.png",
"caption": "Чарівний верстат",
"u": 0,
"v": 0,
"width": 110,
"height": 110
},
"crystal_ore": {
"location": "ebwizardry:textures/gui/handbook/pictures/crystal_ore.png",
"caption": "Кришталева руда",
"u": 0,
"v": 0,
"width": 64,
"height": 64
},
"magic_crystal": {
"location": "ebwizardry:textures/items/crystal_magic.png",
"caption": "Чарівний кристал",
"u": 6,
"v": 6,
"width": 36,
"height": 33,
"texture_width": 48,
"texture_height": 48,
"border": false
},
"magic_wand": {
"location": "ebwizardry:textures/items/wand_novice.png",
"caption": "Чарівна паличка",
"u": 0,
"v": 0,
"width": 64,
"height": 64,
"border": false
},
"crystal_flower": {
"location": "ebwizardry:textures/gui/handbook/pictures/crystal_flower.png",
"caption": "Кришталева квітка",
"u": 0,
"v": 0,
"width": 48,
"height": 48
},
"wizard_tower": {
"location": "ebwizardry:textures/gui/handbook/pictures/wizard_tower.png",
"caption": "Вежа чаклуна",
"u": 0,
"v": 0,
"width": 110,
"height": 110
},
"wizard_armour": {
"location": "ebwizardry:textures/gui/handbook/pictures/wizard_armour.png",
"caption": "Набір чаклунської броні",
"u": 0,
"v": 0,
"width": 90,
"height": 90
},
"classes": {
"location": "ebwizardry:textures/gui/handbook/pictures/classes.png",
"caption": "три класи",
"u": 0,
"v": 0,
"width": 96,
"height": 32,
"border": false
},
"obelisk": {
"location": "ebwizardry:textures/gui/handbook/pictures/obelisk.png",
"caption": "обеліск",
"u": 0,
"v": 0,
"width": 110,
"height": 67
},
"shrine": {
"location": "ebwizardry:textures/gui/handbook/pictures/shrine.png",
"caption": "святилище землі",
"u": 0,
"v": 0,
"width": 110,
"height": 67
},
"library_ruins": {
"location": "ebwizardry:textures/gui/handbook/pictures/library_ruins.png",
"caption": "руїни бібліотеки",
"u": 0,
"v": 0,
"width": 110,
"height": 57
},
"remnant": {
"location": "ebwizardry:textures/gui/handbook/pictures/remnant.png",
"caption": "Вогняний залишок",
"u": 0,
"v": 0,
"width": 110,
"height": 73
},
"tiers": {
"location": "ebwizardry:textures/gui/handbook/pictures/tiers.png",
"caption": "4 рівні жезлів ",
"u": 0,
"v": 0,
"width": 96,
"height": 24,
"border": false
},
"elements": {
"location": "ebwizardry:textures/gui/handbook/pictures/elements.png",
"caption": "7 арканічних елементів",
"u": 0,
"v": 0,
"width": 106,
"height": 20,
"border": false
}
},
"recipes": {
"arcane_workbench": {
"locations": [ "ebwizardry:arcane_workbench" ]
},
"magic_wand": {
"locations": [ "ebwizardry:magic_wand" ]
},
"novice_wands": {
"locations": [
"ebwizardry:magic_wand",
"ebwizardry:wand_fire",
"ebwizardry:wand_ice",
"ebwizardry:wand_lightning",
"ebwizardry:wand_necromancy",
"ebwizardry:wand_earth",
"ebwizardry:wand_sorcery",
"ebwizardry:wand_healing"
]
},
"magic_missile_spell_book": {
"locations": [ "ebwizardry:magic_missile_spell_book" ]
},
"wizard_handbook": {
"locations": [ "ebwizardry:wizard_handbook" ]
},
"crystal_flower_to_crystals": {
"locations": [ "ebwizardry:crystal_flower_to_crystals" ]
},
"crystal_blocks": {
"locations": [
"ebwizardry:crystal_block",
"ebwizardry:crystal_block_fire",
"ebwizardry:crystal_block_ice",
"ebwizardry:crystal_block_lightning",
"ebwizardry:crystal_block_necromancy",
"ebwizardry:crystal_block_earth",
"ebwizardry:crystal_block_sorcery",
"ebwizardry:crystal_block_healing"
]
},
"crystal_blocks_to_crystals": {
"locations": [
"ebwizardry:crystal_block_to_crystals",
"ebwizardry:crystal_block_to_crystals_fire",
"ebwizardry:crystal_block_to_crystals_ice",
"ebwizardry:crystal_block_to_crystals_lightning",
"ebwizardry:crystal_block_to_crystals_necromancy",
"ebwizardry:crystal_block_to_crystals_earth",
"ebwizardry:crystal_block_to_crystals_sorcery",
"ebwizardry:crystal_block_to_crystals_healing"
]
},
"crystal_shard": {
"locations": [ "ebwizardry:crystal_shard" ]
},
"small_mana_flask": {
"locations": [ "ebwizardry:small_mana_flask" ]
},
"medium_mana_flask": {
"locations": [ "ebwizardry:medium_mana_flask" ]
},
"large_mana_flask": {
"locations": [ "ebwizardry:large_mana_flask" ]
},
"runestone": {
"locations": [
"ebwizardry:runestone_fire",
"ebwizardry:runestone_ice",
"ebwizardry:runestone_lightning",
"ebwizardry:runestone_necromancy",
"ebwizardry:runestone_earth",
"ebwizardry:runestone_sorcery",
"ebwizardry:runestone_healing"
]
},
"runestone_pedestal": {
"locations": [
"ebwizardry:runestone_pedestal_fire",
"ebwizardry:runestone_pedestal_ice",
"ebwizardry:runestone_pedestal_lightning",
"ebwizardry:runestone_pedestal_necromancy",
"ebwizardry:runestone_pedestal_earth",
"ebwizardry:runestone_pedestal_sorcery",
"ebwizardry:runestone_pedestal_healing"
]
},
"transportation_stone": {
"locations": [ "ebwizardry:transportation_stone" ]
},
"magic_silk": {
"locations": [ "ebwizardry:magic_silk" ]
},
"wizard_hat": {
"locations": [ "ebwizardry:wizard_hat" ]
},
"wizard_robe": {
"locations": [ "ebwizardry:wizard_robe" ]
},
"wizard_leggings": {
"locations": [ "ebwizardry:wizard_leggings" ]
},
"wizard_boots": {
"locations": [ "ebwizardry:wizard_boots" ]
},
"blank_scroll": {
"locations": [ "ebwizardry:blank_scroll" ]
},
"firebomb": {
"locations": [ "ebwizardry:firebomb" ]
},
"poison_bomb": {
"locations": [ "ebwizardry:poison_bomb" ]
},
"smoke_bomb": {
"locations": [ "ebwizardry:smoke_bomb" ]
},
"spark_bomb": {
"locations": [ "ebwizardry:spark_bomb" ]
},
"gilded_wood": {
"locations": [
"ebwizardry:gilded_oak_wood",
"ebwizardry:gilded_spruce_wood",
"ebwizardry:gilded_birch_wood",
"ebwizardry:gilded_jungle_wood",
"ebwizardry:gilded_acacia_wood",
"ebwizardry:gilded_dark_oak_wood"
]
},
"bookshelves": {
"locations": [
"ebwizardry:oak_bookshelf",
"ebwizardry:spruce_bookshelf",
"ebwizardry:birch_bookshelf",
"ebwizardry:jungle_bookshelf",
"ebwizardry:acacia_bookshelf",
"ebwizardry:dark_oak_bookshelf"
]
},
"lecterns": {
"locations": [
"ebwizardry:oak_lectern",
"ebwizardry:spruce_lectern",
"ebwizardry:birch_lectern",
"ebwizardry:jungle_lectern",
"ebwizardry:acacia_lectern",
"ebwizardry:dark_oak_lectern"
]
},
"receptacle": {
"locations": [ "ebwizardry:receptacle" ]
}
},
"sections": {
"inside_cover": {
"centre": {
"x": true,
"y": true
},
"text": [
"Підручник чарівника",
"За видавництвом Electroblob"
]
},
"introduction": {
"title": "Introduction",
"text": [
"Вітаю, чарівники! Ця книга пояснює численні шляхи володіння магією та способи її використання. Проте це не звичайна книга — її сторінки з’являтимуться перед вами по мірі того, як ви відкриватимете для себе чарівний світ.",
"Використовуйте стрілки, щоб переходити від сторінки до сторінки, а кнопки з подвійними стрілками — щоб швидко переходити між розділами. Використовуйте центральну кнопку меню, щоб повернутися на головну сторінку змісту.",
"Натисніть на будь-який фіолетовий текст-посилання, щоб одразу перейти на відповідну сторінку. Натисніть праву кнопку миші на закладці, щоб перемістити її на поточну сторінку, і ліву кнопку — щоб повернутися до закладеної сторінки."
]
},
"main_contents": {
"title": "Contents",
"contents": {
"id": "main_contents",
"hyperlinks": true,
"page_numbers": true,
"separator": "."
}
},
"setting_up": {
"title": "налаштування",
"include_in_contents": "main_contents",
"text": [
"Задля того що б розпочати свій шлях становлення чарівником вам знадобится:",
"- Створення чарівної палички:",
"#recipe magic_wand",
"- An @arcane_workbench Чарівний верстат@, створюєтся так:",
"#recipe arcane_workbench",
"- A @spells spell@ book. Їх можна знайти в скринях, отримати як здобич, купити у чарівників, або створити початкове заклинання — «Магічну ракету», як показано:",
"#recipe magic_missile_spell_book",
"також вам знадобится чимало магічних кристалів що б у вашої палични не закінчилась @mana@."
]
},
"mana": {
"title": "Mana",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:crystal"
],
"text": [
"Мана — це таємнича енергія, що надає чарівникам їхні здібності. Вона не має власної фізичної форми; скоріше, це всюдисуща аура, яка пронизує весь світ. Особливо вона проявляється у природних кристалах, що зустрічаються в надрах скель. @wands@ здатні зберігати ману, і саме з них вона спрямовується у @spells@, які ви накладаєте. Кожне заклинання потребує різної кількості мани — залежно від його сили та тривалості дії.",
"#image crystal_ore",
"#image magic_crystal",
"Загальноприйнято, що ману неможливо створити чи знищити, і що коли накладається заклинання, мана, яку воно використовує, просто розсіюється у навколишньому світі. Насправді саме так існує більшість мани — тонким шаром, розподіленим по всьому світу. Проте, щоб вона стала корисною, її потрібно сконцентрувати: природним шляхом протягом тисячоліть, як у випадку з підземними кристалами, або штучно — складна тема, яка не розглядається у цій книзі. Існують також різні способи зробити заклинання більш економними щодо мани та відновлювати частину мани, що розсіюється під час накладання заклинань."
]
},
"wands": {
"title": "Wands",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:arcane_initiate"
],
"text": [
"Чарівна паличка — головний інструмент чарівника. За її допомогою ви можете накладати будь-яке @spells@, за умови що паличка здатна вмістити його силу (див. @tiers@). Існує багато різновидів чарівних паличок, але майже напевно ви почнете з базової магічної палички.",
"#image magic_wand",
"Більшість чарівних паличок починають своє існування як просте поєднання певного магічного кристала, прикріпленого до дерев’яної палиці, з золотим самородком на іншому кінці. Кристал, безперечно, є джерелом сили палички, адже саме він забезпечує фокус, необхідний для спрямування @spells@. Проте решта палички також має значення: вона не лише дозволяє зручно тримати її в руці, а й слугує шляхом, яким @mana@ може проходити, спрямовуватися та набувати форми.",
"У міру того як за допомогою палички накладається дедалі більше заклинань, вона стає ефективнішою у спрямуванні @spells@ і, відповідно, здатною використовувати заклинання більшої потужності. Про це свідчать ледь помітні зміни у формі та зовнішньому вигляді самої палички: зі зростанням її сили кристал стає яскравішим, його колір може змінюватися залежно від @elements@, а з часом деревина, з якої зроблено паличку, починає рости й закручуватися у дедалі складніші форми.",
"Коли ви тримаєте чарівну паличку, в куті екрана з’являється невеликий інтерфейс. Він показує поточне вибране заклинання разом із його піктограмою, а також — якщо заклинання було застосоване — смугу перезарядки, що відображає час до можливості повторного використання. Також відображаються назви наступного та попереднього заклинань, прив’язаних до палички.\nЩоб перемикатися між заклинаннями, використовуйте клавіші #next_spell_key та #previous_spell_key (їх можна змінити в меню «Налаштування → Керування»). Крім того, ви можете перемикати заклинання, прокручуючи коліщатко миші під час присідання.",
"Під час перегляду інвентарю наведення курсора на чарівну паличку показує, скільки @mana@ у ній збережено, а також поточне вибране @spells заклинання@ і будь-які особливі здібності, які вона може мати. Більш детальну інформацію про паличку можна переглянути, розмістивши її на @arcane_workbench чарівному верстаку@."
]
},
"spells": {
"title": "Spells",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:handbook/spells"
],
"text": [
"A @wands wand@ марні без заклинань, які можна з їх допомогою накладати. Заклинання зберігаються у двох формах: у книгах, які є постійними, та у сувоях, що мають тимчасову дію.",
"Книги заклинань самі по собі мають небагато користі; натомість їх використовують, щоб прив’язати заклинання, яке вони містять, до @wands@. Книги заклинань можна знайти по всьому світу, і вам не доведеться довго шукати, щоб натрапити на одну з них. Коли ви знайдете таку книгу, наведіть на неї курсор, щоб з першого погляду побачити основну інформацію про заклинання. Ви також можете клацнути правою кнопкою миші, тримаючи книгу заклинань, щоб прочитати детальніший опис.",
"Сувої, навпаки, виконують іншу функцію: клацання правою кнопкою миші, тримаючи сувій, накладає заклинання, прив’язане до нього, при цьому сувій знищується. Як і книги заклинань, сувої можна знайти по всьому світу, але їх також можна створювати самостійно (див. @enchanting_scrolls@).",
"Сувої дуже корисні, коли потрібно використати заклинання один-два рази для певного завдання, але не хочеться займати ним слот у вашій @wands@. Вони також мають перевагу в тому, що дозволяють миттєво накладати заклинання, які зазвичай потребують часу на зарядку. Слід зауважити, однак, що порівняно з використанням палички, сувої є менш ефективним способом накладання заклинань.",
"Для непідготовленого ока магічні руни, якими написані заклинання, є нерозбірливими. Щоб ідентифікувати заклинання, можна, звісно, накласти його і подивитися, що станеться — хоча це може спричинити небажані наслідки, а багато заклинань потребують певних умов для свого застосування. Крім того, завжди існує ймовірність помилкового прочитання невідомого заклинання під час його накладання, з потенційно небезпечними наслідками залежно від сили заклинання. Більш безпечним і надійним способом є використання сувою ідентифікації, який можна знайти в скринях або придбати у чарівника. Клацання правою кнопкою миші, тримаючи такий сувій, ідентифікує першу невідому книгу заклинань або сувій на вашій панелі швидкого доступу, при цьому сувій ідентифікації витрачається."
]
},
"arcane_workbench": {
"title": "Arcane Workbench",
"include_in_contents": "main_contents",
"contents": {
"id": "arcane_workbench_subsections",
"hyperlinks": true,
"page_numbers": true,
"separator": "."
},
"triggers": [
"ebwizardry:handbook/arcane_workbench"
],
"text": [
"Чарівний верстат — це блок, зовні схожий на стіл чарувань, на якому можна заряджати, покращувати та прив’язувати @spells@ до ваших @wands@. Його можна створити за таким рецептом:",
"#recipe arcane_workbench",
"Наступні сторінки пояснюють різні дії, які можна виконувати на чарівному верстаті.",
"#image workbench"
],
"sections": {
"binding_spells": {
"title": "Binding Spells",
"include_in_contents": "arcane_workbench_subsections",
"triggers": [
"ebwizardry:handbook/arcane_workbench"
],
"text": [
"Що б прив'язати @spells spell@ до вашої @wands палички@, просто помістіть паличку в центральний слот верстаку, потім покладіть книгу заклинань в один із навколишніх слотів і натисніть «Застосувати». Ви помітите, що книга заклинань при цьому залишається цілою — це тому, що процес прив’язування заклинання не «забирає» його з книги, а налаштовує паличку на це заклинання. Таким чином, ви можете змінювати заклинання, прив’язані до вашої палички, скільки забажаєте, просто повторюючи процес прив’язування. Одночасно до палички можна прив’язати до п’яти заклинань, хоча це число можна збільшити за допомогою покращень налаштування."
]
},
"charging_wands": {
"title": "Charging Wands",
"include_in_contents": "arcane_workbench_subsections",
"triggers": [
"ebwizardry:handbook/arcane_workbench"
],
"text": [
"Що б зарядити @wands wand@, помістіть їх в центральний слот арканної верстати та покладіть кілька магічних кристалів у нижній лівий слот, після чого натисніть «Застосувати». Кожен кристал дає #mana_per_crystal @mana@, і паличка візьме лише стільки, скільки їй потрібно, тому ви можете тримати запас кристалів у верстаті для подальшого використання. Проте пам’ятайте, що паличка може взяти лише ціле число кристалів, тому якщо, наприклад, паличці потрібно ще 30 одиниць мани, #example_charging_loss мани буде втрачено при заряджанні."
]
},
"upgrading_wands": {
"title": "Upgrading Wands",
"include_in_contents": "arcane_workbench_subsections",
"triggers": [
"ebwizardry:handbook/tome_of_arcana"
],
"text": [
"Що б покращити @wands wand@, вам знадобиться том аркани відповідного @tiers@ або спеціальне покращення для палички. Обидва ці предмети можна знайти в здобичі або купити у чарівника. Щоб покращити паличку, помістіть її в центральний слот, а покращення — у верхній правий слот, після чого натисніть «Застосувати».",
"Спеціальні покращення для палички покращують певний аспект палички. Кожен тип покращення можна застосувати до трьох разів, а загальна кількість покращень, які можна встановити на паличку, залежить від її рівня. Покращення не можна видалити."
]
},
"enchanting_scrolls": {
"title": "Enchanting Scrolls",
"include_in_contents": "arcane_workbench_subsections",
"triggers": [
"ebwizardry:handbook/scrolls"
],
"text": [
"Таємнича робоча майстерня також може використовуватися для чарування @spells spell@ scrolls. Для цього вам знадобиться чистий сувій, створений з паперу та нитки, книга заклинань для обраного заклинання та достатня кількість магічних кристалів, щоб надати @mana@ для його використання.",
"#recipe blank_scroll",
"Помістіть кристали в нижній лівий слот, чистий сувій у центральний слот, а книгу заклинань у верхній слот над ним, потім натисніть «Підтвердити», щоб зачарувати сувій."
]
}
}
},
"wizard_armour": {
"title": "Wizard Armor",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:arcane_initiate"
],
"text": [
"Як магу, вам знадобиться щось, що захистить вас від створінь, з якими ви боретеся. Звичайна броня не підійде. Щоб максимально використовувати свої заклинання, вам знадобиться магічна броня: капелюх, ризи, штани та чоботи. На відміну від звичайної броні, вона не ламається. Замість цього вона використовує магічну енергію, щоб захищати носія. Це, звісно, означає, що її потрібно заряджати магічними кристалами, так само як і чарівну паличку. На щастя, ці предмети не витрачають багато @mana@. Заряджайте їх так само, як паличку, у @arcane_workbench арканічній майстерні@ або за допомогою @mana_flasks@, але будьте обережні – якщо у них закінчиться мана, ви залишитесь беззахисними.",
"#image wizard_armour",
"Магічну броню можна створити з магічного шовку:",
"#recipe magic_silk",
"#recipe wizard_hat",
"#recipe wizard_robe",
"#recipe wizard_leggings",
"#recipe wizard_boots",
"Магічну броню можна зачарувати за допомогою звичайного стола зачарувань. Через особливі властивості магічного шовку, вона добре утримує чари. Особливо корисними для чарівника є зачарування «Захист від магії», «Захист від холоду» та «Захист від електрики», які допомагають оборонятися від інших магів.",
"Чарівники, що присвячують себе практиці одного елемента, іноді носять особливий одяг, який надає бонуси для цього елемента. Повні комплекти такого одягу дуже цінуються серед чарівників і знайти їх не так просто.",
"Опановуючи основи магії, багато чарівників обирають певний шлях або клас, щоб продовжити своє навчання. Існує три основні класи, до яких чарівник може належати: мудрець, бойовий маг і чорнокнижник. Членів кожного класу легко впізнати за їхніми ризами, які модифіковані — зазвичай із рідкісних та екзотичних матеріалів — щоб краще відповідати їхнім потребам.",
"#image classes",
"Мудрець",
"Присвятивши життя пошуку знань, мудрець вдосконалив мистецтво ефективного накладання заклять, що дозволяє йому використовувати мінімальну кількість мани. Вишуканість його шовкових риз приховує значний захист, адже мудрець також є вправним чарівником — хоча він все ще не рівняється з міцною металевою бронею. Проте тривале перебування у безпеці бібліотеки робить його дещо повільним у бою.",
"Бойовий маг",
"Бойовий маг готовий до бою, одягнений у поєднання міцної тканини та кристально-срібної броні, створеної найвмілішими майстрами. Завдяки дисципліні та тренуванням, бойовий маг також покращив швидкість і реакцію, що дозволяє йому частіше накладати закляття. Проте це не обходиться дешево, і бойовий маг має бути обережним, щоб не витратити ману занадто швидко.",
"Чорнокнижник",
"Чорнокнижник живе для досліджень, постійно розширюючи межі можливого, щоб віднайти забуті таємниці магії. Це, разом із готовністю експериментувати з тим, що інші вважають забороненим, дозволяє їм порушувати обмеження традиційного навчання, накладати закляття з вражаючою швидкістю та зменшувати витрати мани. Їхні легкі шатра, хоч і зручні для досліджень, забезпечують менший захист у бою порівняно з іншими магами."
]
},
"magical_world": {
"title": "Magic in the World",
"include_in_contents": "main_contents",
"contents": {
"id": "magical_world_subsections",
"hyperlinks": true,
"page_numbers": true,
"separator": "."
},
"triggers": [
"ebwizardry:handbook/on_subsection_unlock"
],
"text": [
"Не можна опанувати магію, лишаючись вдома — світ повен чарів, які чекають на дослідження! Стародавні руїни, реліквії та загадкові істоти, добрі і злі, чекають на відкриття — якщо знати, де шукати. Найвеличніші чарівники також є найбільш допитливими і здобувають багато своїх знань та сили через дослідження оточення та експерименти з тим, що вони знаходять.",
"Хоча більшу частину часу чарівник проводить наодинці, обмін знаннями також є життєво важливим для опанування магії. Шукай інших практиків магії та навчайся від них якнайбільше: спілкування, можливо, є найпотужнішою силою з усіх."
],
"sections": {
"crystal_flowers": {
"title": "Crystal Flowers",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:handbook/crystal_flowers"
],
"text": [
"Під час своїх мандрів ти, ймовірно, час від часу натрапиш на дивовижні осяйні квіти, що ростуть у дикій природі. Ці характерні рослини відомі як кристалічні квіти, і вони дивовижно ефективні в концентрації @mana@ — досі ніхто точно не знає, чому. Ми, однак, знаємо, що їх можна збирати та переробляти для отримання кристалів мани, що робить їх доволі корисним джерелом мани у верхньому світі. Проте кількість мани, яку можна отримати таким способом, обмежена через невеликий розмір квітів та через те, що вони ростуть лише невеликими групами.",
"#image crystal_flower",
"#recipe crystal_flower_to_crystals"
]
},
"wizard_towers": {
"title": "Wizard Towers",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:wizard_tower"
],
"text": [
"Не бійтеся — ви не єдиний досліджувач магії в цьому світі. Під час своїх мандрів ви, ймовірно, натрапите на одну чи дві високі вежі з характерним гострим дахом. Це оселя іншого мага. Життя у самоті іноді робить чарівників трохи дратівливими, проте зазвичай вони дружні й готові поділитися своїми знаннями з вами — хоча й за певну ціну. Очікуйте, що доведеться заплатити дорогоцінними металами та камінням, а натомість ви отримаєте безліч чудових таємничих винаходів.",
"Якщо ж ви шукаєте закляття рівня @tiers майстер@, вам доведеться звернутися до спеціаліста.",
"#image wizard_tower",
"На жаль, у цьому світі трапляються й чарівники, які не раді гостям. Зазвичай це вигнанці, ворожі до кожного, хто стає на їхньому шляху. Наближаючись до таких осіб, будьте обережні та будьте готові захищатися від могутньої магії. Проте здолавши їх, ви зможете заволодіти їхніми знаннями.",
"Пам’ятайте також: жоден чарівник не стерпить нападу чи злодійства. У таких випадках вони без роздумів самі вершитимуть правосуддя."
]
},
"obelisks": {
"title": "Обеліски",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:handbook/obelisks"
],
"text": [
"Сліди давньої магії розкидані по всьому світу, і найпомітнішими з них є висічені з каменю споруди, вкриті безліччю символів і рун. Ці руїни — усе, що залишилося від, імовірно, якоїсь давньої цивілізації. Хто б або що б їх не створив, вони безсумнівно володіли значними знаннями магії та використали їх, щоб накласти на ці місця захисні чари, які зберігаються й донині.",
"Ці споруди загалом поділяються на два типи. Перший, і найпоширеніший з них, — це обеліски: високі шпилі з різьбленого каменю, відомого як рунний камінь, з відкритою нижньою частиною, у якій зберігаються незначні арканні реліквії забутого минулого.",
"#image obelisk",
"Ці споруди зазвичай захищені чарами, що призивають ворожих @creatures магічних істот@, якщо будь-яка людина підійде надто близько. Відбийте атаку цих істот і знищіть джерело їхньої сили — і реліквії стануть вашими."
]
},
"shrines": {
"title": "Святилища",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:visit_shrine"
],
"text": [
"Другий, рідкісніший, тип споруд відомий як святилище. Ці споруди складаються з кола стовпів із рунного каменю, що оточують центральний п’єдестал, на якому стоїть скриня, наповнена древніми @artefacts артефактами@. Ця скриня зазвичай захищена чарами арканного замка, які не дозволяють нікому, окрім власника, відкрити її. Уся споруда також перебуває під дією поля утримання, яке не дозволяє нічому, що підійшло надто близько, вибратися назовні.",
"#image shrine",
"Через свою величезну арканну силу й значущість, не кажучи вже про скарби всередині, ці споруди особливо приваблюють будь-якого початківця-чарівника — але будьте обережні. Існує чимало повідомлень про чарівників, які опинялися замкненими всередині полів утримання й поступово божеволіли, можливо, через клаустрофобію. Втім, дедалі більше людей вірять, що такі випадки є навмисною частиною захисної магії святилища, а чарівники, які опинилися в пастці, насправді перебувають під контролем і змушені охороняти споруду. Воістину моторошна перспектива для кожного, хто наважиться підійти надто близько..."
]
},
"library_ruins": {
"title": "Руїни бібліотеки",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:find_library_ruins"
],
"text": [
"Чарівники зазвичай накопичують чималу кількість книг заклять протягом свого життя, тож для них цілком звично будувати бібліотеку, аби зберігати свою колекцію. Деякі особливо потайні чарівники навіть зберігали свої арканні знання під землею, подалі від сторонніх очей.\nОднак бібліотека потребує догляду, і якщо її власник помирає, не залишивши учня, який успадкує книги, споруда швидко занепадає. Тисячі років використання магії призвели до того, що дедалі більше таких бібліотек залишилися покинутими, і під час мандрівок вам цілком може трапитися одна з них. Їх часто варто обшукати в надії знайти книги заклять або інші предмети, які змогли пережити руйнівний плин часу..",
"#image library_ruins",
"Бібліотеки використовуються не лише для зберігання книг. Чарівники минулого використовували силу жертовника для імбуювання — проведення ритуалів елементального насичення магічних матеріалів. Розташування жертовника — це точне й складне мистецтво, оскільки він повинен бути вирівняний відповідно до певних небесних рухів і місцевих коливань мани.\n\nУ сучасності практика будівництва таких жертовників стала значно рідкіснішою, адже міцний кам’яний жертовник служить століттями, і потреби у нових майже немає. Натомість більшість чарівників знаходять і відновлюють існуючі жертовники, коли потрібно виконати ритуали імбуювання — а руїни бібліотек є ідеальним місцем для таких пошуків.",
"Ви також колись можете вважати за корисне збудувати власну бібліотеку, для чого, звісно, знадобляться книжкові полиці:",
"#recipe bookshelves",
"#recipe gilded_wood",
"Ці книжкові полиці — це не просто меблі. Якщо ввести в них найпотужніші магічні кристали, вони дозволяють легко передавати знання між собою та найближчим @arcane_workbench арканічним верстаком@ — дуже зручно, коли ваша колекція зростає. Не менш корисним є й кафедра:",
"#recipe lecterns",
"Том, який левітує над кафедрою, збирає знання з навколишніх книжок зі закляттями, дозволяючи переглядати їх у зручний час, не запам’ятовуючи точне розташування кожної."
]
},
"creatures": {
"title": "Чарівні істоти",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:handbook/magical_creatures"
],
"text": [
"Окрім чарівників, численні інші істоти теж використовують магію. Багато з них мають арканне походження, і більшість можна викликати за допомогою певних магічних @spells заклять@. Ці істоти можуть охороняти @obelisks обеліск@ або служити потужному заклиначеві. Менші арканні створіння іноді зустрічаються навіть у дикій природі.",
"Вартий уваги приклад — залишок (remnant), духоподібна істота, що утворюється природно з відлуння потужної магії і зазвичай набирає одну з семи арканічних @elements елементів@. Поодинці вони майже не становлять загрози, але зграя може швидко подолати непідготовленого мандрівника, а їхня вроджена стійкість до магії робить бажаним використання лука або іншої зброї дальньої дії.",
"#image remnant",
"Once slain, their spectral dust can be collected, which, besides emitting a colorful glow, has a variety of useful properties. A receptacle, crafted as shown, provides a convenient means of containing it.",
"#recipe receptacle",
"Після того, як залишок (remnant) буде знищений, можна зібрати його спектральний пил (spectral dust), який, крім яскравого світіння, володіє різноманітними корисними властивостями. Зручним способом його зберігання є спеціальний резервуар, створений за відповідним рецептом."
]
},
"artefacts": {
"title": "Артефакти",
"include_in_contents": "magical_world_subsections",
"triggers": [
"ebwizardry:artefact"
],
"text": [
"Під час дослідження @shrines святилища@ вам може пощастити знайти стародавній магічний артефакт — предмет, здатний надавати його власникові унікальні та потужні бонуси й спеціальні здібності. Відомі три типи таких артефактів: кільця, які посилюють заклинання та додають ефекти, амулети, що покращують захисні здібності, і обереги, що надають корисні утилітарні ефекти. Ці артефакти діють лише тоді, коли вони правильно одягнені; просто носіння їх при собі недостатньо.."
]
}
}
},
"tiers": {
"title": "Tiers",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:handbook/tome_of_arcana"
],
"text":[
"@wands Жезли@ та @spells чари@ Існує чотири рівні: #colour_noviceпочатківець#colour_reset, #colour_apprenticeучень#colour_reset, #colour_advancedпідмайстер #colour_resetand #colour_masterмайстер#colour_reset. кожен рівень потужніший за попередній.",
"#image tiers",
"#colour_noviceПочаткові #colour_resetжезли Це ті, що можна створити. Вони вміщують до #novice_max_charge мани і можуть використовувати лише закляття рівня новачка. Закляття новачка досить прості, щоб їх міг застосувати будь-хто, і зазвичай вони не витрачають багато мани. Це не означає, що вони марні на вищих рівнях; їхня низька вартість робить деякі закляття новачка корисними навіть тоді, коли у вас є доступ до заклять майстерського рівня.",
"#colour_apprenticeУчнівські #colour_resetжезли Наступний рівень після жезлів новачка. Вони вміщують до #apprentice_max_charge мани і можуть використовувати закляття рівня новачка та учня. Закляття учня трохи потужніші за закляття новачка, але зазвичай коштують більше мани..",
"#colour_advancedПідмайстерські #colour_resetжезли Досить рідкісні та значно потужніші. Вони можуть використовувати всі закляття, крім майстерських, і вміщують #advanced_max_charge мани. Закляття рівня «підмайстер» можуть надавати надлюдські здібності, такі як невидимість, і завдавати серйозної шкоди ворогам.",
"#colour_resetжезли #colour_masterМайстрів #colour_resetНаймогутніші чарівні палички, що існують. Вони вміщують до #master_max_charge мани та можуть використовувати будь-яке закляття. Книги заклять #colour_masterмайстерського #colour_resetрівня надзвичайно рідкісні, а їхні закляття здатні завдати повного спустошення не лише ворогам, а й самому світу. Використовуйте обережно.",
"Щоб паличка могла бути підвищена до просунутішого ступеню, вона спершу повинна стати достатньо сильною, щоб проводити закляття цього рівня. Палички набувають сили, коли з їхньою допомогою використовують закляття. Різноманітність заклять, їхня сила, ефекти стихій та інші зовнішні чинники можуть впливати на те, як швидко паличка досягає повної зрілості.",
"Як тільки паличка стане достатньо сильною, том аркани надає останній каталізатор, необхідний для підвищення палички до наступного рівня (див. @upgrading_wands Підвищення рівня жезлів@), і після цього процес дозрівання може початися знову для наступного рівня."
]
},
"elements": {
"title": "Елементи",
"include_in_contents": "main_contents",
"triggers": [
"ebwizardry:handbook/elements"
],
"text": [
"@spells чарам@ притаманні різні елементи, які визначають природу закляття і також надають бонуси при використанні з певними @wands жезлами@.",
"#image elements",
"#colour_fireВогонь#colour_reset \nМожливо, найруйнівніший елемент — вогонь, який пов’язаний із жаром, лавою та вибухами. Потужний піромант може випустити справжнє пекло на своїх ворогів і, ймовірно, спалити навколишній світ у процесі. Більшість заклять вогню підпалюють ціль, завдаючи поступової шкоди з часом. Проте остерігайтеся: закляття вогню можуть не мати ефекту на істот із Пекла.",
"#colour_iceЛід#colour_reset \nЕлемент льоду складається зі всього холодного. Закляття морозу часто сповільнюють ворогів або повністю їх заморожують, і вони особливо ефективні проти вогняних істот. Магія морозу також може бути корисною поза боєм, наприклад, для заморожування води, щоб перейти через річку.",
"#colour_lightningБлискавка#colour_reset \nЦей елемент пов’язаний із блискавкою, бурями та погодою. Потужний маг бурі є силою, з якою варто рахуватися, і деякі здатні викликати блискавку на вимогу. Закляття блискавки часто завдають шкоди кільком ворогам одночасно та зазвичай самостійно шукають свою ціль, що робить їх ефективною атакою проти будь-яких істот… але остерігайтеся кріперів.",
"#colour_necromancyНекромантія#colour_reset \nНекромантія — це елемент темряви, хаосу та нежиті. Некроманти загадкові і часто вважаються злими, хоча насправді це трапляється рідко. Закляття некромантії зазвичай використовуються для виклику @creatures сутностей@, щоб вони боролися на вашому боці, або навіть для підкорення волі ваших ворогів.",
"#colour_earthЗемля#colour_reset \nЕлемент Землі зосереджений на природному світі: тваринах, рослинах, вітрі та подібному. Магія Землі дуже різноманітна і може проявлятися у багатьох формах — від отруєння ворогів до вивільнення гніву погоди. Закляття Землі поєднують атаку, захист і корисні ефекти.",
"#colour_sorcerySorcery#colour_reset \nМістика це елемент сили та змін. Чарівники маніпулюють світлом, гравітацією і навіть самою реальністю, щоб відповідати своїм потребам. Містичні чари можуть використовуватися, зокрема, для надання магічних сил своєму творцю або переміщення предметів за своєю волею.",
"#colour_healingЗцілення#colour_reset \nЕлемент лікування стосується захисту та відновлення. Цілителі прагнуть захищати себе та своїх союзників якомога ефективніше, і тому вони можуть бути дуже складними противниками. Хоча зазвичай закляття лікування не використовуються для атаки, очищаюче світло деяких лікувальних заклять завдає значної шкоди нежиті. Закляття лікування є незамінними в бою.",
"Межі між різними елементами не завжди чіткі, і деякі закляття мають властивості інших елементів, відмінних від їхнього власного.",
"Якщо вам пощастить, ви можете натрапити на елементальний кристал. Елементальні кристали — це магічні кристали, які поглинули властивості певного елемента під час свого зростання, хоча точні умови та механізм цього явища залишаються загадкою. Відомо лише, що такі кристали можна використовувати для створення елементарних жезлів, які дозволяють накладати закляття відповідного елемента з більшою силою, ніж зазвичай."
]
},
"miscellaneous": {
"title": "Miscellaneous",
"include_in_contents": "main_contents",
"contents": {
"id": "miscellaneous_subsections",
"hyperlinks": true,
"page_numbers": true,
"separator": "."
},
"triggers": [
"ebwizardry:on_subsection_unlock"
],
"sections": {
"mana_flasks": {
"title": "Mana Flasks",
"include_in_contents": "miscellaneous_subsections",
"triggers": [
"ebwizardry:handbook/mana_flasks"
],
"text": [
"Аркендурова Компанія Магічних Принад – магічні предмети на будь-який смак!",
"Потрібно підзарядити @wands жезл@ на ходу? Та нема питань! @mana Мана@ тепер може бути закоркована. Просто створіть Ману у пляшці з скляної пляшки та восьми магічних кристалів і візьміть її з собою. Коли вам знадобиться її використати, просто застосуйте її разом зі своєю паличкою, і вона відновить частину її заряду. *",
"#recipe medium_mana_flask",
"НОВИНА! Представляємо абсолютно нові малі та великі пляшки з маною – тепер ви можете обрати розмір пляшки відповідно до своїх потреб!",
"#recipe small_mana_flask",
"#recipe large_mana_flask",
"* Цей процес зруйнує пляшку. Певна кількість мани втрачається під час розливу..."
]
},
"throwable_items": {
"title": "Throwable Items",
"include_in_contents": "miscellaneous_subsections",
"triggers": [
"ebwizardry:handbook/throwables"
],
"text": [
"Різні @spells заклинання@ створюють фізичні предмети, деякі з яких також можна виготовити безпосередньо. Вогняні бомби, отруйні бомби, іскрові бомби та димові бомби можна скрафтити:",
"#recipe firebomb",
"#recipe poison_bomb",
"#recipe smoke_bomb",
"#recipe spark_bomb"
]
},
"automated_casting": {
"title": "Automated Casting",
"include_in_contents": "miscellaneous_subsections",
"triggers": [
"ebwizardry:enchant_scroll"
],
"text": [
"Недавні експерименти показали, що можна автоматизувати використання @spells заклинань@ до певного рівня, використовуючи лише простий диспенсер. Ніхто точно не знає чому, але здається, що дивні властивості редстоуну навіть поширюються на активацію заклинальних сувоїв. Помістивши кілька сувоїв у диспенсер і подавши живлення, можна досягти потрібного ефекту...."
]
}
}
},
"crafting_recipes": {
"title": "Crafting Recipes",
"include_in_contents": "main_contents",
"text": [
"#recipe arcane_workbench",
"#recipe novice_wands",
"#recipe magic_missile_spell_book",
"#recipe wizard_handbook",
"#recipe crystal_flower_to_crystals",
"#recipe crystal_blocks",
"#recipe crystal_blocks_to_crystals",
"#recipe crystal_shard",
"#recipe small_mana_flask",
"#recipe medium_mana_flask",
"#recipe large_mana_flask",
"#recipe runestone",
"#recipe runestone_pedestal",
"#recipe transportation_stone",
"#recipe magic_silk",
"#recipe wizard_hat",
"#recipe wizard_robe",
"#recipe wizard_leggings",
"#recipe wizard_boots",
"#recipe blank_scroll",
"#recipe firebomb",
"#recipe poison_bomb",
"#recipe smoke_bomb",
"#recipe spark_bomb",
"#recipe gilded_wood",
"#recipe bookshelves",
"#recipe lecterns",
"#recipe receptacle"
]
},
"credits": {
"title": "Credits",
"include_in_contents": "main_contents",
"text": [
"Electroblob's Wizardry \nVersion #version \nFor Minecraft #mcversion",
"Розроблено, запрограмовано та оформлено Electroblob’ом",
"Дякуємо Minecraft Forge та MCP, без яких цей мод не був би можливим.",
"Також дякую спільноті моддерів Minecraft, яка завжди знаходить відповіді на мої проблеми з модами!",
"Крім того, я хотів би подякувати наступним особам за їхній внесок у мод:",
"Код:",
"- Corail31 \n- 12foo \n- Shadows-of-Fire \n- HellFirePvP \n- Tora-B \n- Avatair \n- Aeronica \n- UltraHex \n- Azim-Palmer \n- raoulvdberge \n- rafasoares \n- xinyuan-liu \n- SettingDust",
"Переклад:",
"- Spanish: MadWrist, Alsentar \n- Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon, bigenergy, MugGod2, DrHesperus \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese (Simplified): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Korean: shejery, rewi_wire, 방통, red1854th \n- Polish: Trozuu, Olej \n- German: BirdyDragon, Lemopav \n- Chinese (Traditional): chesterccj305 \n- Hungarian: Bombadil",
"Звукові ефекти:",
"- OhhWowProductions\n- fredzed\n- deleted_user_3277771\n- DiscoveryME\n- OGSoundFX\n- leosalom\n- juskiddink",
"For more information, check out the @https://github.com/Electroblob77/Wizardry/wiki wiki@.",
"Can't get enough wizardry? Join the @https://discord.gg/hs8yJP2 Discord server@ для останніх новин, обговорень та додаткових матеріалів!"
]
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Some files were not shown because too many files have changed in this diff Show More