Compare commits

...

49 Commits

Author SHA1 Message Date
WinDanesz d6087dfb9b feat: Implement Arcane Lock synchronization packet handling. Fixes #900 GeckoLib incompat 2026-06-25 21:29:14 +02:00
Xvareon 768a756723 Fixed a ClassCastError crash in bookshelf loot tables; fixes #899 (#909) 2026-06-01 19:08:19 +02:00
WinDanesz ae7064f5be fix: more potion effect desync fixed 2026-05-12 15:12:13 +00:00
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
WinDanesz 65ed0e8a22 version bump to 4.3.14 2024-05-18 16:07:11 +02:00
WinDanesz 936601cbad fix: Fixed a bug that caused stackable artefacts to alway double their effect 2024-05-18 15:57:15 +02:00
129 changed files with 8084 additions and 2562 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.13",
"1.12.2-recommended": "4.3.13"
"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.13"
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,10 @@ public class CommonProxy {
public void handleConquerShrinePacket(PacketConquerShrine.Message message){}
public void handleBombExplosionPacket(PacketBombExplosion.Message message){}
public void handleArcaneLockSyncPacket(PacketSyncArcaneLock.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.13";
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();
}
@@ -274,7 +274,7 @@ public final class WizardryEventHandler {
if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud)
&& !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving())
&& !(attacker instanceof FakePlayer)) // Fake players cause problems
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost,
if(!world.isRemote) attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost,
Spells.ice_shroud.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.ice_shroud.getProperty(Spell.EFFECT_STRENGTH).intValue()));
@@ -326,7 +326,7 @@ public final class WizardryEventHandler {
level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.freezing_weapon,
attacker.getHeldItemMainhand());
// Frost lasts for longer because it doesn't do any actual damage
if(level > 0 && !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
if(!event.getEntityLiving().world.isRemote && level > 0 && !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
event.getEntityLiving().addPotionEffect(new PotionEffect(WizardryPotions.frost, level * 200, 0));
}
}
@@ -338,7 +338,7 @@ public final class WizardryEventHandler {
int level = event.getSource().getImmediateSource().getEntityData()
.getInteger(FreezingWeapon.FREEZING_ARROW_NBT_KEY);
if(level > 0 && !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
if(!event.getEntityLiving().world.isRemote && level > 0 && !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
event.getEntityLiving().addPotionEffect(new PotionEffect(WizardryPotions.frost, level * 150, 0));
}
@@ -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);
}
@@ -62,7 +62,7 @@ public class BlockPermafrost extends BlockDryFrostedIce {
entity.attackEntityFrom(DamageSource.MAGIC, Spells.permafrost.getProperty(Spell.DAMAGE).floatValue());
int duration = Spells.permafrost.getProperty(Spell.EFFECT_DURATION).intValue();
int amplifier = Spells.permafrost.getProperty(Spell.EFFECT_STRENGTH).intValue();
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, duration, amplifier));
if(!world.isRemote) ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, duration, amplifier));
}
// EntityLivingBase's slipperiness code doesn't get the block below it properly so slipperiness only works for
@@ -68,9 +68,11 @@ public class BlockSnare extends Block implements ITileEntityProvider {
entity.attackEntityFrom(source, Spells.snare.getProperty(Spell.DAMAGE).floatValue());
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS,
Spells.snare.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.snare.getProperty(Spell.EFFECT_STRENGTH).intValue()));
if(!world.isRemote){
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS,
Spells.snare.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.snare.getProperty(Spell.EFFECT_STRENGTH).intValue()));
}
if(!world.isRemote) world.destroyBlock(pos, false);
}
@@ -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,79 @@ 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 handleArcaneLockSyncPacket(PacketSyncArcaneLock.Message message){
if(Minecraft.getMinecraft().world != null){
TileEntity tileentity = Minecraft.getMinecraft().world.getTileEntity(message.pos);
if(tileentity != null){
if(message.locked){
tileentity.getTileData().setUniqueId(electroblob.wizardry.spell.ArcaneLock.NBT_KEY, message.owner);
}else{
electroblob.wizardry.util.NBTExtras.removeUniqueId(tileentity.getTileData(), electroblob.wizardry.spell.ArcaneLock.NBT_KEY);
}
}
}
}
@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 +952,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;
@@ -60,8 +60,12 @@ public class RenderArcaneLock {
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.enableDepth();
GlStateManager.depthMask(true);
GlStateManager.disableCull();
lighting = GL11.glIsEnabled(GL11.GL_LIGHTING);
GlStateManager.disableLighting();
GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
@@ -90,6 +94,7 @@ public class RenderArcaneLock {
tessellator.draw();
GlStateManager.enableCull();
GlStateManager.disableBlend();
GlStateManager.enableTexture2D();
if(lighting){
@@ -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)));
@@ -62,7 +62,7 @@ public class EntityBlizzard extends EntityScaledConstruct {
}
// All entities are slowed, even the caster (except those immune to frost effects)
if(!MagicDamage.isEntityImmune(DamageType.FROST, target))
if(!world.isRemote && !MagicDamage.isEntityImmune(DamageType.FROST, target))
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 20, 0));
}
@@ -81,7 +81,7 @@ public class EntityEarthquake extends EntityMagicConstruct { // NOT a scaled con
target.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.BLAST),
10 * this.damageMultiplier);
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1));
if(!world.isRemote) target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1));
}
// All targets are thrown, even those immune to the damage, so they don't fall into the ground.
@@ -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);
@@ -145,7 +145,7 @@ public class EntityWitheringTotem extends EntityScaledConstruct {
for(EntityLivingBase target : nearby){
if(EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this,
if(!world.isRemote && EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this,
getCaster(), DamageType.MAGIC), damage)){
target.addPotionEffect(new PotionEffect(MobEffects.WITHER, Spells.withering_totem.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.withering_totem.getProperty(Spell.EFFECT_STRENGTH).intValue()));
@@ -32,11 +32,11 @@ public class EntityHuskMinion extends EntityZombieMinion {
boolean flag = super.attackEntityAsMob(target);
if(flag && this.getHeldItemMainhand().isEmpty() && target instanceof EntityLivingBase){
if(flag && !this.world.isRemote && this.getHeldItemMainhand().isEmpty() && target instanceof EntityLivingBase){
float f = this.world.getDifficultyForLocation(new BlockPos(this)).getAdditionalDifficulty();
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.HUNGER, 140 * (int)f));
}
return flag;
}
}
}
@@ -119,7 +119,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
target.motionX += this.getLookVec().x * 0.2;
target.motionZ += this.getLookVec().z * 0.2;
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0));
if(!target.world.isRemote) target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0));
this.applyEnchantments(this, target);
@@ -120,7 +120,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
&& ((EntityLivingBase)this.getRidingEntity()).getHealth() > 0){
if(this.ticksExisted % 16 == 1){
this.getRidingEntity().attackEntityFrom(DamageSource.MAGIC, 1);
if(this.getRidingEntity() != null){ // Some mobs force-dismount when attacked (normally when dying)
if(this.getRidingEntity() != null && !this.world.isRemote){ // Some mobs force-dismount when attacked (normally when dying)
((EntityLivingBase)this.getRidingEntity())
.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 20, 2));
}
@@ -129,7 +129,7 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
seconds = 15;
}
if(seconds > 0){
if(seconds > 0 && !target.world.isRemote){
target.addPotionEffect(new PotionEffect(MobEffects.POISON, seconds * 20, 0));
}
}
@@ -139,7 +139,7 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
@Override
public void onSuccessfulAttack(EntityLivingBase target){
target.addPotionEffect(new PotionEffect(MobEffects.WITHER, 200));
if(!target.world.isRemote) target.addPotionEffect(new PotionEffect(MobEffects.WITHER, 200));
}
@Override
@@ -359,7 +359,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
this.updateRecipes = false;
}
this.addPotionEffect(new PotionEffect(MobEffects.REGENERATION, 200, 0));
if(!this.world.isRemote) this.addPotionEffect(new PotionEffect(MobEffects.REGENERATION, 200, 0));
}
}
@@ -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){
@@ -33,7 +33,7 @@ public class EntityDarknessOrb extends EntityMagicProjectile {
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.WITHER).setProjectile(),
damage);
if(target instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.WITHER, target))
if(!this.world.isRemote && target instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.WITHER, target))
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER,
Spells.darkness_orb.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.darkness_orb.getProperty(Spell.EFFECT_STRENGTH).intValue()));
@@ -27,7 +27,7 @@ public class EntityDart extends EntityMagicArrow {
@Override
public void onEntityHit(EntityLivingBase entityHit){
// Adds a weakness effect to the target.
entityHit.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, Spells.dart.getProperty(Spell.EFFECT_DURATION).intValue(),
if(!entityHit.world.isRemote) entityHit.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, Spells.dart.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.dart.getProperty(Spell.EFFECT_STRENGTH).intValue(), false, false));
this.playSound(WizardrySounds.ENTITY_DART_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
}
@@ -59,4 +59,4 @@ public class EntityDart extends EntityMagicArrow {
public int getLifetime(){
return -1;
}
}
}
@@ -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();
}
}
@@ -48,7 +48,7 @@ public class EntityIceCharge extends EntityBomb {
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FROST).setProjectile(),
damage);
if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
if(!this.world.isRemote && entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(WizardryPotions.frost,
Spells.ice_charge.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(),
Spells.ice_charge.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue()));
@@ -39,7 +39,7 @@ public class EntityIceLance extends EntityMagicArrow {
public void onEntityHit(EntityLivingBase entityHit){
// Adds a freeze effect to the target.
if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
if(!entityHit.world.isRemote && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost,
Spells.ice_lance.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.ice_lance.getProperty(Spell.EFFECT_STRENGTH).intValue()));
@@ -64,4 +64,4 @@ public class EntityIceLance extends EntityMagicArrow {
@Override
protected void entityInit(){}
}
}
@@ -37,7 +37,7 @@ public class EntityIceShard extends EntityMagicArrow {
public void onEntityHit(EntityLivingBase entityHit){
// Adds a freeze effect to the target.
if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
if(!entityHit.world.isRemote && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost,
Spells.ice_shard.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.ice_shard.getProperty(Spell.EFFECT_STRENGTH).intValue()));
@@ -67,4 +67,4 @@ public class EntityIceShard extends EntityMagicArrow {
@Override
protected void entityInit(){}
}
}
@@ -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(!this.world.isRemote && 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);
@@ -83,13 +69,15 @@ public class EntityPoisonBomb extends EntityBomb {
target.attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON),
Spells.poison_bomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier);
target.addPotionEffect(new PotionEffect(MobEffects.POISON,
Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_DURATION).intValue(),
Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_STRENGTH).intValue()));
if(!this.world.isRemote){
target.addPotionEffect(new PotionEffect(MobEffects.POISON,
Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_DURATION).intValue(),
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);
@@ -62,11 +48,11 @@ public class EntitySmokeBomb extends EntityBomb {
for(EntityLivingBase target : targets){
if(target != this.getThrower()){
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
if(!this.world.isRemote) 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();
@@ -49,7 +49,6 @@ public final class WizardryBaublesIntegration {
ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.BELT, BaubleType.BELT);
ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.BODY, BaubleType.BODY);
ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.HEAD, BaubleType.HEAD);
ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.TRINKET, BaubleType.TRINKET);
}
public static boolean enabled(){
@@ -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){
@@ -93,8 +93,7 @@ public class ItemArtefact extends Item {
/** An artefact that improves utility spells. One of these can be active at any one time. */ CHARM(1),
/** Added for add-on artefacts. */ BELT(1),
/** Added for add-on artefacts. */ BODY(1),
/** Added for add-on artefacts. */ HEAD(1),
/** Added for add-on artefacts. */ TRINKET(1);
/** Added for add-on artefacts. */ HEAD(1);
public final int maxAtOnce;
@@ -676,7 +675,7 @@ public class ItemArtefact extends Item {
}else if(artefact == WizardryItems.amulet_transience){
if(player.getHealth() <= 6 && player.world.rand.nextFloat() < 0.25f){
if(!player.world.isRemote && player.getHealth() <= 6 && player.world.rand.nextFloat() < 0.25f){
player.addPotionEffect(new PotionEffect(WizardryPotions.transience, 300));
player.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, 300, 0, false, false));
}
@@ -702,7 +701,7 @@ public class ItemArtefact extends Item {
}else if(artefact == WizardryItems.ring_ice_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
if(!player.world.isRemote && EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.ICE){
event.getEntityLiving().addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 0));
}
@@ -735,14 +734,14 @@ public class ItemArtefact extends Item {
}else if(artefact == WizardryItems.ring_necromancy_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
if(!player.world.isRemote && EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.NECROMANCY){
event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.WITHER, 200, 0));
}
}else if(artefact == WizardryItems.ring_earth_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
if(!player.world.isRemote && EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.EARTH){
event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0));
}
@@ -775,12 +774,12 @@ public class ItemArtefact extends Item {
}else if(artefact == WizardryItems.ring_soulbinding){
// Best guess at necromancy spell damage: either it's wither damage...
if((event.getSource() instanceof IElementalDamage
if(!player.world.isRemote && ((event.getSource() instanceof IElementalDamage
&& (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.WITHER))
// or it's direct, non-melee damage and the player is holding a wand with a necromancy spell selected
|| (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY))){
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY)))){
event.getEntityLiving().addPotionEffect(new PotionEffect(WizardryPotions.curse_of_soulbinding, 400));
CurseOfSoulbinding.getSoulboundCreatures(WizardData.get(player)).add(event.getEntity().getUniqueID());
@@ -805,14 +804,14 @@ public class ItemArtefact extends Item {
}else if(artefact == WizardryItems.ring_poison){
// Best guess at earth spell damage: either it's poison damage...
if((event.getSource() instanceof IElementalDamage
if(!player.world.isRemote && ((event.getSource() instanceof IElementalDamage
&& (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.POISON))
// ...or it was from a dart...
|| event.getSource().getImmediateSource() instanceof EntityDart
// ...or it's direct, non-melee damage and the player is holding a wand with an earth spell selected
|| (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.EARTH))){
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.EARTH)))){
event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0));
}
@@ -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()));
}
@@ -96,7 +96,7 @@ public class ItemFrostAxe extends ItemAxe implements IConjuredItem {
@Override
public boolean hitEntity(ItemStack stack, EntityLivingBase target, EntityLivingBase wielder){
if(!MagicDamage.isEntityImmune(DamageType.FROST, target))
if(!target.world.isRemote && !MagicDamage.isEntityImmune(DamageType.FROST, target))
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 160, 1));
return false;
}
@@ -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) {
@@ -209,7 +209,7 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana
@Override
public void onArmorTick(World world, EntityPlayer player, ItemStack itemStack){
if(armorType == EntityEquipmentSlot.HEAD && player.ticksExisted % 20 == 0
if(!world.isRemote && armorType == EntityEquipmentSlot.HEAD && player.ticksExisted % 20 == 0
&& isWearingFullSet(player, element, ArmourClass.BATTLEMAGE) && doAllArmourPiecesHaveMana(player)){
player.addPotionEffect(new PotionEffect(WizardryPotions.ward, 219, 0, true, false));
}
@@ -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);
@@ -86,7 +86,13 @@ public class RandomSpell extends LootFunction {
// (And WHY does it only return an entity?! The underlying field is always a player so I'm casting it anyway)
EntityPlayer player = (EntityPlayer)context.getKillerPlayer();
Spell spell = pickRandomSpell(stack, random, spellContext, player);
Spell spell;
try {
spell = pickRandomSpell(stack, random, spellContext, player);
} catch (ClassCastException e) {
Wizardry.logger.warn("Failed to apply random_spell function! An item ({}) was passed to a spell's compatibility check that it couldn't handle.", stack.getItem().getRegistryName(), e);
return stack; // Safely bypass processing and return the raw item intact
}
if(spell == Spells.none) Wizardry.logger.warn("Tried to apply the random_spell loot function to an item, but no"
+ " enabled spells matched the criteria specified. Substituting placeholder (metadata 0) item.");
@@ -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);
@@ -257,9 +257,9 @@ public abstract class Forfeit {
1, EntityUtils.canDamageBlocks(p, w)));
}));
add(Tier.NOVICE, Element.ICE, create("freeze_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200))));
add(Tier.NOVICE, Element.ICE, create("freeze_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200)); }));
add(Tier.APPRENTICE, Element.ICE, create("freeze_self_2", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 1))));
add(Tier.APPRENTICE, Element.ICE, create("freeze_self_2", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 1)); }));
add(Tier.APPRENTICE, Element.ICE, create("ice_spikes", (w, p) -> {
if(!w.isRemote){
@@ -342,7 +342,7 @@ public abstract class Forfeit {
add(Tier.ADVANCED, Element.LIGHTNING, create("lightning", (w, p) -> w.addWeatherEffect(new EntityLightningBolt(w, p.posX, p.posY, p.posZ, false))));
add(Tier.ADVANCED, Element.LIGHTNING, create("paralyse_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.paralysis, 200))));
add(Tier.ADVANCED, Element.LIGHTNING, create("paralyse_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(WizardryPotions.paralysis, 200)); }));
add(Tier.ADVANCED, Element.LIGHTNING, create("lightning_wraiths", (w, p) -> {
if(!w.isRemote){
@@ -367,7 +367,7 @@ public abstract class Forfeit {
}
}));
add(Tier.NOVICE, Element.NECROMANCY, create("nausea", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 400))));
add(Tier.NOVICE, Element.NECROMANCY, create("nausea", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 400)); }));
add(Tier.APPRENTICE, Element.NECROMANCY, create("zombie_horde", (w, p) -> {
if(!w.isRemote){
@@ -381,7 +381,7 @@ public abstract class Forfeit {
}
}));
add(Tier.ADVANCED, Element.NECROMANCY, create("wither_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.WITHER, 400))));
add(Tier.ADVANCED, Element.NECROMANCY, create("wither_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.WITHER, 400)); }));
add(Tier.MASTER, Element.NECROMANCY, create("cripple_self", (w, p) -> p.attackEntityFrom(DamageSource.MAGIC, p.getHealth() - 1)));
@@ -422,7 +422,7 @@ public abstract class Forfeit {
}
}));
add(Tier.APPRENTICE, Element.EARTH, create("poison_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.POISON, 400, 1))));
add(Tier.APPRENTICE, Element.EARTH, create("poison_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.POISON, 400, 1)); }));
add(Tier.ADVANCED, Element.EARTH, create("flood", (w, p) -> {
if(!w.isRemote && EntityUtils.canDamageBlocks(p, w)){
@@ -457,7 +457,7 @@ public abstract class Forfeit {
add(Tier.APPRENTICE, Element.SORCERY, create("teleport_self", (w, p) -> ((Banish)Spells.banish).teleport(p, w, 8 + w.rand.nextDouble() * 8)));
add(Tier.ADVANCED, Element.SORCERY, create("levitate_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.LEVITATION, 200))));
add(Tier.ADVANCED, Element.SORCERY, create("levitate_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.LEVITATION, 200)); }));
add(Tier.ADVANCED, Element.SORCERY, create("vex_horde", (w, p) -> {
if(!w.isRemote){
@@ -500,16 +500,16 @@ public abstract class Forfeit {
}
}));
add(Tier.APPRENTICE, Element.HEALING, create("hunger", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.HUNGER, 400, 4))));
add(Tier.APPRENTICE, Element.HEALING, create("hunger", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.HUNGER, 400, 4)); }));
add(Tier.APPRENTICE, Element.HEALING, create("blind_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 200))));
add(Tier.APPRENTICE, Element.HEALING, create("blind_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 200)); }));
add(Tier.ADVANCED, Element.HEALING, create("weaken_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 600, 3))));
add(Tier.ADVANCED, Element.HEALING, create("weaken_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 600, 3)); }));
add(Tier.ADVANCED, Element.HEALING, create("jam_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, 300))));
add(Tier.ADVANCED, Element.HEALING, create("jam_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, 300)); }));
add(Tier.MASTER, Element.HEALING, create("curse_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE))));
add(Tier.MASTER, Element.HEALING, create("curse_self", (w, p) -> { if(!w.isRemote) p.addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE)); }));
}
}
}
@@ -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);
}
}
}
@@ -0,0 +1,57 @@
package electroblob.wizardry.packet;
import electroblob.wizardry.Wizardry;
import io.netty.buffer.ByteBuf;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* <b>[Server -> Client]</b> This packet is sent when a container is locked or unlocked by Arcane Lock to update clients.
*/
public class PacketSyncArcaneLock implements IMessageHandler<PacketSyncArcaneLock.Message, IMessage> {
@Override
public IMessage onMessage(Message message, MessageContext ctx){
if(ctx.side.isClient()){
net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleArcaneLockSyncPacket(message));
}
return null;
}
public static class Message implements IMessage {
public BlockPos pos;
public boolean locked;
public UUID owner;
public Message(){}
public Message(BlockPos pos, boolean locked, @Nullable UUID owner){
this.pos = pos;
this.locked = locked;
this.owner = owner != null ? owner : new UUID(0, 0);
}
@Override
public void fromBytes(ByteBuf buf){
this.pos = BlockPos.fromLong(buf.readLong());
this.locked = buf.readBoolean();
this.owner = new UUID(buf.readLong(), buf.readLong());
}
@Override
public void toBytes(ByteBuf buf){
buf.writeLong(pos.toLong());
buf.writeBoolean(locked);
buf.writeLong(owner.getMostSignificantBits());
buf.writeLong(owner.getLeastSignificantBits());
}
}
}
@@ -35,6 +35,8 @@ 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);
registerMessage(PacketSyncArcaneLock.class, PacketSyncArcaneLock.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")));
@@ -2,6 +2,8 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.SpellActions;
import electroblob.wizardry.packet.PacketSyncArcaneLock;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.util.*;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
@@ -13,14 +15,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 {
@@ -70,12 +76,14 @@ public class ArcaneLock extends SpellRay {
if(world.getPlayerEntityByUUID(tileentity.getTileData().getUniqueId(NBT_KEY)) == player){
NBTExtras.removeUniqueId(tileentity.getTileData(), NBT_KEY);
world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3);
WizardryPacketHandler.net.sendToDimension(new PacketSyncArcaneLock.Message(pos, false, null), world.provider.getDimension());
return true;
}
}else{
// Locking
tileentity.getTileData().setUniqueId(NBT_KEY, player.getUniqueID());
world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3);
WizardryPacketHandler.net.sendToDimension(new PacketSyncArcaneLock.Message(pos, true, player.getUniqueID()), world.provider.getDimension());
return true;
}
}
@@ -167,4 +175,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
@@ -36,15 +36,15 @@ public class Telekinesis extends SpellRay {
target.motionZ = (origin.z - target.posZ) / 6;
return true;
} else if (target instanceof EntityPlayer && (Wizardry.settings.telekineticDisarmament && !ItemArtefact.isArtefactActive((EntityPlayer) target, WizardryItems.amulet_anchoring)) {
} 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?
if(!player.getHeldItemMainhand().isEmpty()){
if (!player.getHeldItemMainhand().isEmpty()) {
if(!world.isRemote){
if (!world.isRemote) {
EntityItem item = player.entityDropItem(player.getHeldItemMainhand(), 0);
// Makes the item move towards the caster
item.motionX = (origin.x - player.posX) / 20;
@@ -52,7 +52,7 @@ public class Telekinesis extends SpellRay {
}
player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY);
return true;
}
}
@@ -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){
entity.addPotionEffect(new PotionEffect(WizardryPotions.containment, 219));
// 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) {
if(!world.isRemote) 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);
}

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