Compare commits

...

174 Commits

Author SHA1 Message Date
Electroblob77 f37812be3e That's one heck of a commit you've got there...
I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
2019-08-18 00:04:18 +01:00
Electroblob77 2680d02304 Reduce excessive call limits based on crash report in issue #72 2019-01-06 18:00:24 +00:00
Electroblob77 f564fdce60 Add DamageSafetyChecker, which implements an if-all-else-fails fix for cross-mod infinite looping in attack/hurt/damage events, and changes relevant calls to attack methods to use the safety checker methods instead. Also adds damage source blacklist and compatibility warnings config options. Fixes #72. 2019-01-06 17:51:41 +00:00
Electroblob77 3bdee06738 Merge branch '1.12.2' into 1.12.2-dev 2019-01-06 16:26:02 +00:00
Electroblob77 31673cf247 Missed in b374f71 2019-01-06 15:35:56 +00:00
Electroblob77 f6f36a30fd Register resource reload listener for handbook, missed in 1356905 2019-01-06 15:35:18 +00:00
Electroblob77 2db96cf18d Change spell chat messages to hotbar toast notifications 2019-01-06 15:31:48 +00:00
Electroblob77 b8ba7d1ea4 Transportation stone tweak 2019-01-06 15:21:08 +00:00
Electroblob77 b33037fd1b Missed in 1356905 2019-01-05 23:18:52 +00:00
Electroblob77 a378fe86f5 Retexture all the things! 2019-01-05 23:18:30 +00:00
Electroblob77 5dfa58bf27 Add more spell HUD skins! 2019-01-05 23:12:54 +00:00
Electroblob77 740bfb1a8f Replace old spell book GUI texture with four retextured tiered variants and tweak the layout a tiny bit 2019-01-05 23:12:03 +00:00
Electroblob77 03e8682260 Replace calls to Item#isInCreativeTab(...) with a direct check, fixes metadata items not being displayed correctly in the creative inventory 2019-01-05 23:04:02 +00:00
Electroblob77 4ca89b2dca Increase all conjured item durations 2019-01-05 23:00:49 +00:00
Electroblob77 212fbe51df Move block, item, potion and enchantment registries over to the @ObjectHolder system and reorganise the potion classes a bit 2019-01-05 22:59:45 +00:00
Electroblob77 eb2abdfbe9 Move wand and armour map retrieval methods to WizardryItems, encapsulating the underlying maps 2019-01-05 22:52:45 +00:00
Electroblob77 f8cfc59415 Missed in b374f71 2019-01-05 22:48:38 +00:00
Electroblob77 13569058ae Handbook rewrite part 1: Formatting and JSON-ification!
- Everything is now defined in a JSON file
- Formatting is now done dynamically and content wraps properly no matter what font is used
- Added inline hyperlinks
- Added a movable bookmark for quick access to a specific page
- Added next section and previous section buttons to flip through entire sections at once
- Added a contents button visible on every page, which navigates directly to the main contents
2019-01-05 22:38:06 +00:00
Electroblob77 78189ed862 Fix a few things in the HUD skin chooser screen 2019-01-05 22:30:06 +00:00
Electroblob77 09185e5f1c Introduce DrawingUtils to centralise GUI rendering utility methods 2019-01-05 22:28:57 +00:00
Electroblob77 201f276c8b More cleanup 2019-01-05 22:19:22 +00:00
Electroblob77 224ee281ef Cleanup 2019-01-05 22:18:14 +00:00
Electroblob77 f907d50333 Fix deobf jar task 2019-01-05 21:14:55 +00:00
Electroblob77 95ce659ec3 Add dev jar task to build.gradle 2019-01-05 18:45:18 +00:00
Electroblob77 f9f6b34549 Update version number 2019-01-05 18:28:06 +00:00
Electroblob77 bee7e51d06 Change key handler to static event handler and remove initialisation from main mod class, fixes #104 2019-01-05 16:42:21 +00:00
Electroblob77 51a2ff0976 Update version number 2019-01-04 22:12:37 +00:00
Electroblob77 1b298ff18b Update credits (all locations) 2019-01-04 22:12:06 +00:00
Electroblob77 86d13d3a6e Swap canSnowAt for canPlaceBlockAt, so it actually works... 2019-01-04 21:58:45 +00:00
Electroblob77 5421372754 Unike input events, tick events fire before the player is initialised, so add a check to stop it crashing with an NPE. 2019-01-04 21:53:39 +00:00
Electroblob77 53b29939f1 Change key handler to listen for ClientTickEvent instead of KeyInputEvent, swap isPressed() to isKeyDown() and restructure code; fixes 'lag'/inconsistent behaviour with mouse button keybinds. (See also #79, not sure if this fixes it since I can't reproduce the problem.) 2019-01-04 21:48:35 +00:00
Electroblob77 7bda8db86f Add check for wand before sending control packets, prevents unnecessary packets being sent 2019-01-04 21:04:06 +00:00
Electroblob77 a7a48cd32b Add a less than max timer check so that magic light does not keep growing indefinitely if the block fails to disappear. Helps reduce the effects of #101 but does not fix it. 2019-01-04 20:53:01 +00:00
Electroblob77 b8e0836a6d Add test for fake players in retaliatory ice shroud effect, fixes #93 2019-01-04 20:48:03 +00:00
Electroblob77 a7e6b2892c Add check to prevent ArrayIndexOutOfBoundsException in WandHelper, fixes #90 (and duplicate issues #80, #91, #97 and #98) 2019-01-04 20:07:02 +00:00
Electroblob77 f886ebfd75 Prevent ice age from placing snow layers at invalid locations, fixes #64 2019-01-04 20:05:09 +00:00
Electroblob77 98b334e6d7 Add condition to only write magic projectile thrower to spawn data if the thrower exists and tweak overrides so it is always written/read last, fixes #71 2019-01-04 20:04:43 +00:00
Electroblob77 83edb8007e Replace direct translation in /allies command with text component, fixes #70 2019-01-04 20:02:57 +00:00
Electroblob77 67dda448f3 Add background tint to arcane workbench GUI, fixes #96 2019-01-04 19:58:31 +00:00
Electroblob77 09953c0299 Add bottom of world check to wizard tower foundation worldgen, fixes #73 2019-01-04 19:57:33 +00:00
Electroblob77 38b0fa65bc Comment out scroll translation console message, fixes #56 2019-01-04 19:56:39 +00:00
Electroblob 7200182c82 Merge pull request #102 from dragon-evol/1.12.2
[zh_cn_handbook update]minor, minor, minor fixes (sorry for disturbing...)
2019-01-03 23:08:10 +00:00
dragon-evol e73e998de2 minor fixes 2019-01-03 13:19:53 +08:00
Electroblob77 b374f71533 The great particle refactoring part 2: Splitting and Streamlining!
- Splits all particle textures into individual files intead of sprite sheets
- Replaces arc and lightning pulse entities with particles
- Pulls particle code up to the general ParticleWizardry so behaviours such as spin can be applied to all particle types
- Renames a few ParticleBuilder methods for conciseness.
- Adds a few new particle effects to various spells and entities, with a slight tweak to EntityMagicArrow#onBlockHit to facilitate impact effects
2019-01-01 21:40:04 +00:00
Electroblob a69da2eb2a Merge pull request #94 from dragon-evol/1.12.2
Update zh_cn.lang and translate the handbook
2018-12-18 00:23:36 +00:00
dragon-evol a00c594d83 Add files via upload 2018-12-17 14:23:23 +08:00
dragon-evol 3df99552e1 Merge pull request #1 from dragon-evol/dragon-evol-patch-1
Update zh_cn.lang
2018-12-17 14:22:20 +08:00
dragon-evol e2bc2824f6 Add files via upload 2018-12-17 14:11:18 +08:00
Electroblob 36f4728f3a Merge pull request #88 from Avatair/feature/mapping-update
Updated MCP Mapping to stable39
2018-11-24 18:20:08 +00:00
alesto 6ca72e7965 Undid adding wizardry dependency. 2018-11-24 09:56:35 +01:00
alesto 151dcc06da Updated mappings 2018-11-22 16:02:40 +01:00
Electroblob 5737109dec Merge pull request #82 from HellFirePvP/1.12.2
Switch enchantment-writing related code to a more direct data-driven approach
2018-11-10 00:48:16 +00:00
HellFirePvP 4bd3a46f32 Switch enchantment-writing related code to a more direct data-driven approach 2018-11-01 13:19:46 +01:00
Electroblob 73e2d64fcd Update guide_for_contributing.md 2018-08-17 23:06:46 +01:00
Electroblob 1f008b405d Update ISSUE_TEMPLATE.md 2018-08-01 23:13:43 +01:00
Electroblob 00e1e2602f Update guide_for_contributing.md 2018-08-01 23:11:27 +01:00
Electroblob 98445da971 Update ISSUE_TEMPLATE.md 2018-08-01 22:59:42 +01:00
Electroblob ad363c64af Correct missing return in lang file 2018-07-03 00:26:44 +01:00
Electroblob 82eed83dcc Correct default scroll direction 2018-07-03 00:24:16 +01:00
Electroblob 7c7a69c755 Add config option to reverse the spell switching scroll direction 2018-07-02 22:56:04 +01:00
Electroblob af01beb583 Spell HUD rewrite and retexturing:
- Replaces the old hardcoded drawing methods with a new json-based system with support for multiple inbuilt and resource pack-defined skins.
- Adds a config option for choosing the spell HUD skin, along with a custom config GUI screen allowing the user to select from a list of available skins with a preview of the currently selected skin.
- Replaces the HUD texture file with a spell_hud subfolder, containing all the necessary textures and json files for the 12 inbuilt skins.
- Adds a subtle sound effect and scroling animation when switching spells
- Reorganises the control event code into a single handler class and converts this class and the spell HUD class to static event handlers.
- Fixes the HUD position option which got broken in the previous spell HUD commit
2018-07-02 22:33:09 +01:00
Electroblob b7cd3db8bc Rename armour fix model 2018-07-02 22:05:19 +01:00
Electroblob 056c35ac3f Add back face to renderer for force arrow 2018-06-24 19:16:11 +01:00
Electroblob f6a24038cc Retexture spell HUD, add different textures for survival/creative mode, and tidy up the GUI code 2018-06-24 19:14:48 +01:00
Electroblob fde5935adc Add visual first person blink effect 2018-06-24 19:11:59 +01:00
Electroblob f8eecfc6d5 Retexture wands 2018-06-24 16:14:17 +01:00
Electroblob 53aa19d1a1 Fix darkness orb spell shooting thunderbolts instead of darkness orbs 2018-06-24 16:13:40 +01:00
Electroblob a49756ddaa Add vector-based position and velocity methods to ParticleBuilder 2018-06-24 16:13:04 +01:00
Electroblob ee3fca35bd Particle tweaks and fixes 2018-06-24 16:12:40 +01:00
Electroblob 55ae4b1161 Miscellaneous cleanup and commenting 2018-06-24 16:08:28 +01:00
Electroblob 76473f7185 Rewrite raytracing methods and optimise their usage 2018-06-24 16:02:34 +01:00
Electroblob ff87729ee8 Missed in 75e5de6 2018-06-24 15:52:59 +01:00
Electroblob 9464b3a084 Retexture leaf particles and move colouring to code 2018-06-24 15:51:28 +01:00
Electroblob d0bf90b900 Retexture flight wings 2018-06-24 15:50:02 +01:00
Electroblob fec4b755bd Remove old unused textures 2018-06-24 15:49:13 +01:00
Electroblob 73d973783d Add continuous spell casting with scrolls and increase scroll and spell book stack sizes to 16 2018-06-24 15:48:48 +01:00
Electroblob 75e5de603e Delegate workbench apply button and slot behaviour to item classes 2018-06-24 12:52:55 +01:00
Electroblob 6da42a18c7 Update references to moved GUI classes 2018-06-24 12:45:49 +01:00
Electroblob f3c9fbdd2e Move GUI classes to separate package 2018-06-24 12:23:08 +01:00
Electroblob c7c7d57e7e Change target blacklist/whitelist checking to use the correct method for retrieving an entity key from EntityList 2018-06-20 23:58:50 +01:00
Electroblob c3abdcf68b Fix NPE in immunity initialisation, fixes #49 2018-06-20 23:57:05 +01:00
Electroblob 8f0727403d Correct fallback translation key for spirit horse 2018-06-20 23:36:15 +01:00
Electroblob 2301e54de4 Prevent potions with custom particles from mixing their particle colour (black) with other potions 2018-06-20 23:35:07 +01:00
Electroblob be952e65f4 Fix incorrect position input in ParticleBuilder's spherical particle spawning convenience method 2018-06-20 23:33:53 +01:00
Electroblob 24457ebe36 Add error prevention and console warning for trying to spawn particles on the server side using ParticleBuilder 2018-06-20 23:32:14 +01:00
Electroblob a523d3dd2b Fix incorrect default scale for flash particles 2018-06-20 23:30:52 +01:00
Electroblob 373fd4c8e8 Add chat readout for applying spells to wizards in creative mode 2018-06-20 23:30:07 +01:00
Electroblob 106148fd9e Miscellaneous cleanup and commenting 2018-06-20 23:28:56 +01:00
Electroblob 29261082f1 Spell hierarchisation and refactoring, plus miscellaneous renaming and minor refactoring
- Adds new generic spell superclasses which aim to group spells such that duplicate code is eliminated, and functions standardised, as much as possible.
- Removes numerous spell classes which are no longer required as a result of this change, and replaces them with instances of the relevant generic spell classes.
- Changes the remaining spell classes to fit with the new system, mostly by having them extend the generic spell classes.
- Completes the transfer of particle spawning to the ParticleBuilder system.
2018-06-20 22:25:54 +01:00
Electroblob dbc1490e10 Merge pull request #53 from ZHENGLOC/1.12.2
Update zh_cn.lang
2018-06-17 11:21:23 +01:00
Electroblob b6400c72a7 Genericise spell casting AI class 2018-06-09 17:36:54 +01:00
Electroblob 719c6d5fbb Add new spell types 2018-06-09 17:33:37 +01:00
ZLOCK_1911 f33a0aceb2 Update zh_cn.lang 2018-06-09 12:02:45 +08:00
Electroblob 7141ae9544 Update CREDITS.md 2018-06-07 17:34:36 +01:00
Electroblob af638ab2f7 Merge pull request #51 from ZHENGLOC/1.12.2
Create zh_cn.lang
2018-06-07 17:32:32 +01:00
ZLOCK_1911 a75a6c6114 Create zh_cn.lang 2018-06-07 18:31:37 +08:00
Electroblob 438f5d7a2e Merge branch '1.12.2' into 1.12.2-dev 2018-06-05 21:44:17 +01:00
Electroblob dec5b1ac2c Add witty/stupid comments to a few classes 2018-06-05 21:26:07 +01:00
Electroblob 726a306830 Delete redundant classes 2018-06-05 21:25:41 +01:00
Electroblob 330c78a92c Change non-evil wizard health from 20 to 30 2018-06-05 21:24:47 +01:00
Electroblob dcec7c7281 Miscellaneous cleanup and commenting 2018-06-05 21:24:09 +01:00
Electroblob b9ba25c116 Add test cases for new particle effects; to be applied to everything once spells have been reorganised 2018-06-05 21:18:01 +01:00
Electroblob 0341a603d5 Missed the imports on that one... 2018-06-05 21:15:02 +01:00
Electroblob d91a3e056a The great particle refactoring part 1: Introducing ParticleBuilder!
Adds ParticleBuilder to replace the particle spawning methods in the proxies, and changes all particle spawning to use the builder except for in the spell classes, which are to be reorganised first.
2018-06-05 21:13:41 +01:00
Electroblob 9c1042f686 Update version number 2018-06-05 17:01:04 +01:00
Electroblob cad3fee1b9 Update mcmod.info 2018-06-05 16:58:33 +01:00
Electroblob 5947b2ee62 Fix smoke bomb recipe and allow charcoal to be used 2018-06-05 16:00:47 +01:00
Electroblob 177a39bad1 Cleanup 2018-06-05 15:43:56 +01:00
Electroblob cdf8f6e32d Remove now-redundant recipe config options 2018-06-05 15:43:48 +01:00
Electroblob f88aac11e9 Convert all non-dynamic recipes to JSON, add ore dictionary support where appropriate and change incorrect "amount" tag to "count", fixes #18 2018-06-05 15:43:14 +01:00
Electroblob 05b905b988 Fix light level detection for petrified creatures breaking out 2018-06-05 14:12:33 +01:00
Electroblob 669cd2892d Add check for valid special wand upgrades, fixes #48 2018-06-05 11:12:03 +01:00
Electroblob dd07d08ff0 Return true in ADS methods when passed a null attacker, acting as a fallback for when caster does not save properly; fixes #40 2018-06-04 23:45:10 +01:00
Electroblob 54bb327fdf Properly sync thrower field for all throwable projectiles, fixes #42 (does not fix vanilla throwables though) 2018-06-04 23:42:12 +01:00
Electroblob 2cd8b46c1f Add toughness to legendary wizard armour 2018-06-04 18:32:32 +01:00
Electroblob b61413f7f1 Remove ISpecialArmor from ItemWizardArmour since it is no longer necessary, fixes #44 2018-06-04 18:20:55 +01:00
Electroblob 4e7765a7e7 Allow tab-completion of spell names in commands without specifying the modid 2018-06-04 18:04:36 +01:00
Electroblob 49a6dc43c1 Cleanup 2018-06-04 17:20:39 +01:00
Electroblob b82bc6698f Make crystal ore drop xp when mined 2018-06-04 17:17:58 +01:00
Electroblob 1e43c67660 Tweak summoned creature targeting so that non-evil wizards' minions no longer attack players unless the player in question has angered the wizard 2018-06-04 17:17:33 +01:00
Electroblob c129571167 Change EntityWizard to implement IMerchant and INpc directly rather than extending EntityVillager, fixes #38 plus a number of other bugs 2018-06-04 16:36:20 +01:00
Electroblob b23a643c0e Make arcane workbench drop items correctly when broken, fixes #20 2018-06-04 15:00:02 +01:00
Electroblob 194bff3e6a Miscellaneous lang file fixes 2018-06-04 13:09:23 +01:00
Electroblob 426ca78288 Sort config options into a more logical order 2018-06-04 12:51:12 +01:00
Electroblob f15919fdce Organise the config file and GUI into categories 2018-06-04 12:39:56 +01:00
Electroblob 2124842136 Fix transportation stone not dropping when unsupported, fixes #23 2018-06-03 23:57:22 +01:00
Electroblob 70e697876b Centralise code for config GUI category classes 2018-06-03 23:24:42 +01:00
Electroblob 9b5e5a871c Add dimension whitelist option for evil wizard spawning and restrict numbers to 1 per chunk, closes #26 2018-06-03 22:22:18 +01:00
Electroblob 5ba92e5de5 Change whitelist/blacklist storage from strings to resourcelocations to bring it in line with the registry system 2018-06-03 22:17:57 +01:00
Electroblob 4e6780a3cf Replace getEntityString with getKey(...).toString() 2018-06-03 18:19:10 +01:00
Electroblob 00584ad635 Move resistances config setup from preInit to init 2018-06-03 18:18:29 +01:00
Electroblob f72ed33515 Add mind control targets blacklist config option 2018-06-03 18:17:05 +01:00
Electroblob c2366ed1ba Change crystal ore drop quantity to drop the intended 1-3 crystals (rather than the previous 1-2) and change fortune behaviour to work like vanilla ores, see discussion in #43 2018-06-03 15:52:13 +01:00
Electroblob 46df8d7311 Add smelting recipe for crystal ore, closes #43 2018-06-03 15:15:50 +01:00
Electroblob d4579b9184 Remove all calls to GlStateManager.pushAttrib() and pullAttrib() and a few other tweaks to gl calls, fixes #12, #46 and #47 2018-06-03 12:39:25 +01:00
Electroblob ee6f0cfe70 Correct death message lang key in code, fixes #41 2018-06-02 16:08:48 +01:00
Electroblob 6155fd9c68 Display non-keyboard keybinds correctly, fixes #36 2018-06-02 15:14:32 +01:00
Electroblob 32c0608b51 Stop spirit horses dropping leather, fixes #37. Also adds the same overrides to spirit wolves; since regular wolves don't drop anything this wasn't noticeable. 2018-06-02 13:39:21 +01:00
Electroblob 404df6af23 Fixes #32 2018-06-02 13:35:37 +01:00
Electroblob 764e10538b Update LICENSE.md 2018-05-21 10:45:11 +01:00
Electroblob 6b1b590e30 Merge pull request #25 from Shadows-of-Fire/client
Fixes #17, #22, and maybe #12
2018-05-04 19:50:11 +01:00
Shadows_of_Fire 8360e520cd De-duplicate armor value 2018-05-04 14:34:09 -04:00
Shadows_of_Fire 005a17d750 Fix armor value tooltips 2018-05-04 14:23:57 -04:00
Shadows_of_Fire 28abe036a0 Armor properly has 0 damage reduction when oom
tooltip is being buggy, won't display armor reduction values.
2018-05-04 08:18:15 -04:00
Shadows_of_Fire ca96112b67 Fix wizard armor breaking 2018-05-04 07:35:51 -04:00
Shadows_of_Fire b8e847c6a6 Fixes #22 2018-05-04 06:53:11 -04:00
Shadows_of_Fire 35cb2c31fc Convert to GlStateManager calls
also removes the weird reflection being used to access the RenderItem
2018-05-04 06:45:57 -04:00
Electroblob 30dd587d34 Move wiki resources to wiki git repository 2018-05-03 20:47:46 +01:00
Electroblob 4644814582 Add wiki resources 2018-05-03 20:31:25 +01:00
Electroblob 7db3151d8a Change hardness of transportation to 0.3, fixes #19 2018-05-02 18:23:07 +01:00
Electroblob c8ca73cca9 Fix incorrect event handler annotation 2018-05-02 13:35:47 +01:00
Electroblob d5a0479ebe Update .gitignore 2018-05-02 13:19:30 +01:00
Electroblob 466b3bbc27 Update .gitignore 2018-05-02 13:17:15 +01:00
Electroblob df14912693 Add new files for the particle refactoring 2018-05-02 13:17:04 +01:00
Electroblob 8a53cc6150 Fix incorrect event handler annotation 2018-05-02 13:03:14 +01:00
Electroblob 339ec69a72 Update version number 2018-04-14 15:53:51 +01:00
Electroblob b2cb717aa3 Cleanup 2018-04-14 15:47:31 +01:00
Electroblob cf4a02f37f Fix crash related to creative tab sorting with HQM installed 2018-04-14 15:28:19 +01:00
Electroblob 84304dd7ce Prevent possible server crash with static initialiser block 2018-04-14 15:26:47 +01:00
Electroblob e3b44eb194 Make entities attacked by summoned creatures revenge-target the summoned creature rather than its summoner 2018-04-14 14:09:16 +01:00
Electroblob 2cbb0a590f Correct position of spell cooldown bar when spell HUD is at top of screen; fixes #14 2018-04-14 13:22:53 +01:00
Electroblob 6b9c864890 Move target position references inside null checks; fixes #13 2018-04-14 13:19:17 +01:00
Electroblob f5730bf945 Replace crossed quads arc renderer with new 3D box-based rendering 2018-04-14 12:51:18 +01:00
Electroblob a1ae3a2b8b Update version number 2018-04-14 12:49:45 +01:00
Electroblob c15a0763d5 Merge branch '1.12.2' of https://github.com/Electroblob77/Wizardry into 1.12.2 2018-04-10 16:04:35 +01:00
Electroblob 4a23d88f30 Add obfuscated names to reflective field access 2018-04-10 16:03:23 +01:00
Electroblob 286705c79b Update README.md 2018-04-09 23:11:23 +01:00
Electroblob 3895f5c531 Cleanup 2018-04-09 17:29:41 +01:00
Electroblob c51e0bd218 Fix mods list logo 2018-04-09 17:29:18 +01:00
Electroblob 766570aeb2 Fix spell book for crystals trade not working 2018-04-09 17:28:59 +01:00
Electroblob 4e91b0d19d Fix broken lang key in config GUI 2018-04-08 00:04:35 +01:00
Electroblob 65f7630546 Update minecraft version number in wizard's handbook 2018-04-08 00:04:06 +01:00
Electroblob 8cf7572041 Update mcmod.info 2018-04-08 00:03:39 +01:00
Electroblob db057a5ca9 Update credits 2018-04-08 00:03:15 +01:00
1849 changed files with 59827 additions and 23705 deletions
+5 -1
View File
@@ -20,5 +20,9 @@ build
# other
eclipse
run
misc
logs
/misc
/libs
libsfordeobf
generated
Thumbs.db
+6 -2
View File
@@ -1,8 +1,8 @@
# Credits
Electroblob's Wizardry
Version 3.1.0
For Minecraft 1.11.2
Version 4.1.4
For Minecraft 1.12.2
Designed, coded and textured by Electroblob
@@ -15,10 +15,14 @@ In addition, I'd like to thank the following individuals for their contributions
#### Code
- Corail31
- 12foo
- Shadows-of-Fire
- HellFirePvP
#### Translations
- Russian: VilagVil
- Spanish and Mexican Spanish: MadWrist
- Chinese: ZHENGLOC and dragon-evol
Lightning ray sound effect from OhhWowProductions
+4 -2
View File
@@ -1,5 +1,7 @@
Minecraft version: 1.10.2 [change as necessary]
Wizardry version: 2.1.0 [change as necessary]
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/guide_for_contributing.md) before posting.
Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.1.4 [change as necessary]
Environment: Singleplayer/LAN game/Server [delete as necessary]
Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.]
+2 -2
View File
@@ -1,7 +1,7 @@
# License
This repository is copyright © Electroblob 2018. The following license is provided to allow you to creatively contribute to and improve the repository.
This license applies only to the source code and assets present in this repository. The compiled mod (available at https://minecraft.curseforge.com/projects/electroblobs-wizardry) comes under its own separate license, see the aforementioned webpage for details.
This license applies only to the source code, assets and wiki pages present in this repository. The compiled mod (available at https://minecraft.curseforge.com/projects/electroblobs-wizardry) comes under its own separate license, see the aforementioned webpage for details.
TL;DR: Do what you want with this repo on Github, but ask me first if you want to put it anywhere else or publish your version. Don't try and make money off the repo or anything in it. I'm not responsible if the code breaks things, and if you help me out by submitting a PR, I can use and modify the contents of that PR as long as I give you credit. (This brief summary is non-binding and does not override or replace the full license below.)
@@ -17,7 +17,7 @@ TL;DR: Do what you want with this repo on Github, but ask me first if you want t
'Electroblob's Wizardry', 'Wizardry', 'the mod' - The Minecraft modification Electroblob's Wizardry
'Repository' - This repository on Github, containing this license, other informational files, configurations, code and assets which are part of Electroblob's Wizardry
'Repository' - This repository on Github, containing this license, the wiki, other informational files, configurations, code and assets which are part of Electroblob's Wizardry
'Curseforge' - The webpage https://minecraft.curseforge.com/projects/electroblobs-wizardry
+1 -1
View File
@@ -2,7 +2,7 @@
![Image not found](https://media-elerium.cursecdn.com/attachments/203/387/banner7.png)
Source code for Electroblob's Wizardry, a Minecraft mod currently for versions 1.7.10, 1.10.2 and 1.11.2. Wizardry adds an RPG-style system of magic spells to Minecraft with the aim of being as playable as possible. No crazy constructs, no perk trees, no complex recipes - simply find spell books, cast spells, and master the arcane!
Source code for Electroblob's Wizardry, a Minecraft mod currently for versions 1.7.10, 1.10.2, 1.11.2, and 1.12.2. Wizardry adds an RPG-style system of magic spells to Minecraft with the aim of being as playable as possible. No crazy constructs, no perk trees, no complex recipes - simply find spell books, cast spells, and master the arcane!
Please read the [guide for contributing](guide_for_contributing.md) before submitting an issue or a PR.
+42 -4
View File
@@ -11,17 +11,39 @@ apply plugin: 'net.minecraftforge.gradle.forge'
//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
version = " 4.1.0 - MC 1.12.2"
version = "4.2.0" // There, it matches semver, happy now? (I am just gonna rename the file afterwards, you know that right?)
group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "Electroblob's Wizardry "
archivesBaseName = "ElectroblobsWizardry"
sourceCompatibility = targetCompatibility = "1.8" // Need this here so eclipse task generates correctly.
compileJava {
sourceCompatibility = targetCompatibility = "1.8"
}
repositories {
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = "http://dvs1.progwml6.com/files/maven"
}
maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "modmaven.k-4u.nl"
}
flatDir {
// evErYthINg hAS tO Be a mAVeN rEPoSiTOrY... (to use deobfProvided, for some reason)
// The lack of documentation is actually quite impressive, see the following threads:
// http://www.minecraftforge.net/forum/topic/44262-solved-how-to-build-mod-with-dependencies/
// https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/mapping-and-modding-tutorials/2866817-setting-up-dependencies-in-minecraft-forge-with
// http://www.minecraftforge.net/forum/topic/44534-adding-dependencies-to-a-forge-project/
dirs "libsfordeobf" // Don't use libs or forge will think you have duplicate mods
}
}
minecraft {
version = "1.12.2-14.23.2.2611"
version = "1.12.2-14.23.5.2814"
runDir = "run"
// the mappings can be changed at any time, and must be in the following format.
@@ -29,7 +51,7 @@ minecraft {
// stable_# stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not always work.
// simply re-run your setup task after changing the mappings to update your workspace.
mappings = "snapshot_20180316"
mappings = "stable_39"
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
}
@@ -38,6 +60,17 @@ dependencies {
// or you may define them like so..
//compile "some.group:artifact:version:classifier"
//compile "some.group:artifact:version"
// compile against the JEI API but do not include it at runtime
deobfProvided "mezz.jei:jei_${mc_version}:${jei_version}:api"
// at runtime, use the full JEI jar
runtime "mezz.jei:jei_${mc_version}:${jei_version}"
//deobfProvided "baubles:baubles-1.12:1.5.2:api" // #ihavenoideawhatimdoing #itjustworks
//runtime "baubles:baubles-1.12:1.5.2" // If in doubt, copy what already works
//deobfProvided "antiqueatlas:antiqueatlas-1.12.2:4.5.1:src"
//runtime "antiqueatlas:antiqueatlas-1.12.2:4.5.1"
// real examples
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
@@ -57,6 +90,11 @@ dependencies {
}
task deobfJar(type: Jar) {
from sourceSets.main.output
classifier = 'deobf'
}
processResources {
// this will ensure that this task is redone when the versions change.
inputs.property "version", project.version
+2
View File
@@ -1,3 +1,5 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
mc_version=1.12.2
jei_version=4.13.1.225
Vendored
+2 -2
View File
@@ -79,14 +79,14 @@ if [ -n "$JAVA_HOME" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
locations of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
locations of your Java installation."
fi
# Increase the maximum file descriptors if we can.
Vendored
+2 -2
View File
@@ -27,7 +27,7 @@ echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo locations of your Java installation.
goto fail
@@ -41,7 +41,7 @@ echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo locations of your Java installation.
goto fail
+14 -1
View File
@@ -2,7 +2,20 @@
## Submitting an issue
When submitting an issue, please stick to the issue template. Please give me as much information about the issue as you can, but keep it relevant. "This is really annoying me and my friend" does not help me fix the problem! Most importantly, use gist or pastebin to post crash reports.
When submitting an issue, I would be grateful if you could follow these few guidelines:
- First and foremost, please check your issue hasn't already been posted. The issue tracker is searchable, so try typing in a few keywords and see if anything comes up.
- Please stick to the issue template, it's there to help you provide the right information so I can fix the problem more easily. If you're not sure what to put for anything, that's ok, just leave it as it is.
- Please give me as much information about the issue as you can, but keep it relevant. "This is really annoying me and my friend" does not help me fix the problem!
- There's no need to put 'tags' in square brackets in the issue title, I'll put proper github issue tags on it which will also help organise it. You can use them to search for issues too!
- Don't close an issue unless it has actually been fixed or is no longer relevant - otherwise I might not end up fixing it at all! Usually I'll take care of closing issues anyway, so you don't have to worry about it.
- Most importantly, use gist or pastebin to post crash reports. Do not paste the entire crash report inline, not even in a code block!
> If you've got a git account (which you do, if you're posting an issue) you can access gist, and pastebin doesn't require an account at all, so there's no excuse!
## Submitting a PR
@@ -1,32 +1,32 @@
package electroblob.wizardry;
import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.PacketClairvoyance;
import electroblob.wizardry.packet.PacketGlyphData;
import electroblob.wizardry.packet.PacketNPCCastSpell.Message;
import electroblob.wizardry.packet.PacketPlayerSync;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.packet.*;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryParticleType;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.translation.I18n;
import net.minecraft.world.World;
import net.minecraftforge.common.config.Property;
import java.util.List;
import java.util.Set;
/**
* The common proxy for wizardry, serving the usual purpose of dealing with all things that need to be handled
* differently on the client and the server. A lot of the methods here appear to do absolutely nothing; this is because
* they do client-only things which are only handled in the client proxy.
*
* @see {@link electroblob.wizardry.client.ClientProxy}
* @see electroblob.wizardry.client.ClientProxy ClientProxy
* @author Electroblob
* @since Wizardry 1.0
*/
@@ -36,90 +36,34 @@ public class CommonProxy {
// SECTION Registry
// ===============================================================================================================
public void registerRenderers(){
}
public void registerRenderers(){}
public void initialiseLayers(){
}
public void initialiseLayers(){}
public void registerKeyBindings(){
}
public void registerSpellHUD(){}
public void registerKeyBindings(){}
public net.minecraft.client.model.ModelBiped getWizardArmourModel(){
return null;
}
public void initGuiBits(){}
public void registerResourceReloadListeners(){}
public void registerSoundEventListener(){}
public void registerAtlasMarkers(){}
// SECTION Particles
// ===============================================================================================================
/**
* Spawns a custom particle of the specified type.
*
* @param type EnumParticleType of the particle
* @param world Reference to the World object
* @param x Particle x position
* @param y Particle y position
* @param z Particle z position
* @param velX Particle x velocity
* @param velY Particle y velocity
* @param velZ Particle z velocity
* @param maxAge Lifetime of the particle in ticks
* @param r Red component of particle colour; will be clamped to between 0 and 1
* @param g Red component of particle colour; will be clamped to between 0 and 1
* @param b Red component of particle colour; will be clamped to between 0 and 1
* @param doGravity Whether the particle is affected by gravity (only affects SPARKLE at the moment)
* @param radius The radius of the particle's motion, for cirular motion particles
*/
public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX,
double velY, double velZ, int maxAge, float r, float g, float b, boolean doGravity, double radius){
// Does nothing since particles are client-side only
}
/** Called from init() in the main mod class to initialise the particle factories. */
public void registerParticles(){} // Does nothing since particles are client-side only
/**
* Spawns a custom particle of the specified type. doGravity defaults to false and radius defaults to 0. Note that
* some of these settings may not affect the particle; some particles are always affected by gravity, for instance.
*
* @param type EnumParticleType of the particle
* @param world Reference to the World object
* @param x Particle x position
* @param y Particle y position
* @param z Particle z position
* @param velX Particle x velocity
* @param velY Particle y velocity
* @param velZ Particle z velocity
* @param maxAge Lifetime of the particle in ticks
* @param r Red component of particle colour; will be clamped to between 0 and 1 (unless this is a MAGIC_FIRE
* particle, in which case this is the scale)
* @param g Red component of particle colour; will be clamped to between 0 and 1
* @param b Red component of particle colour; will be clamped to between 0 and 1
*/
public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX,
double velY, double velZ, int maxAge, float r, float g, float b){
this.spawnParticle(type, world, x, y, z, velX, velY, velZ, maxAge, r, g, b, false, 0);
}
/**
* Spawns a custom particle of the specified type. Colour defaults to white, doGravity defaults to false and radius
* defaults to 0. Note that some of these settings may not affect the particle; some particles are always affected
* by gravity, for instance.
*
* @param type EnumParticleType of the particle
* @param world Reference to the World object
* @param x Particle x position
* @param y Particle y position
* @param z Particle z position
* @param velX Particle x velocity
* @param velY Particle y velocity
* @param velZ Particle z velocity
* @param maxAge Lifetime of the particle in ticks
*/
public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX,
double velY, double velZ, int maxAge){
this.spawnParticle(type, world, x, y, z, velX, velY, velZ, maxAge, 1, 1, 1, false, 0);
/** Creates a new particle of the specified type from the appropriate particle factory. <i>Does not actually spawn the
* particle; use {@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} to spawn particles.</i> */
public electroblob.wizardry.client.particle.ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){
return null;
}
public void spawnTornadoParticle(World world, double x, double y, double z, double velX, double velZ, double radius,
@@ -129,26 +73,23 @@ public class CommonProxy {
// SECTION Items
// ===============================================================================================================
public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
return false;
}
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return null;
}
/**
* Returns the translated name of the scroll, taking spell discovery into account. If, for some reason, this gets
* called server-side, it uses the deprecated server version of I18n, with a warning. Since any likely server-side
* use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since no
* particular player instance can be accessed, spell discovery is ignored.
* called server-side, it uses the deprecated server version of I18n. Since any likely server-side
* use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since
* no particular player instance can be accessed, spell discovery is ignored.
*/
public String getScrollDisplayName(ItemStack scroll){
// I have now learnt that the server side I18n always translates to the default en_US, so I could just return
// a hardcoded name in English instead.
Wizardry.logger.info("A mod has called ItemScroll#getItemStackDisplayName from the server side. Using the"
+ "deprecated server-side translation methods as a fallback.");
// Displays [Empty slot] if spell is continuous.
Spell spell = Spell.get(scroll.getItemDamage());
if(spell.isContinuous) spell = Spells.none;
Spell spell = Spell.byMetadata(scroll.getItemDamage());
return I18n.translateToLocalFormatted("item." + Wizardry.MODID + ":scroll.name",
I18n.translateToLocal("spell." + spell.getUnlocalisedName())).trim();
@@ -158,35 +99,63 @@ public class CommonProxy {
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
}
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style)}, but style defaults to light grey. */
public void addMultiLineDescription(List<String> tooltip, String key){
this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY));
}
/**
* Adds a multi-line description to the given tooltip list. The description is first translated using the given
* translation key, then the formatting code for the given style is appended, and finally the string is word-wrapped
* to the standard width (100).
* @param tooltip The tooltip list to add to
* @param key The translation key for the description
* @param style A style to apply
*/
public void addMultiLineDescription(List<String> tooltip, String key, Style style){}
// SECTION Packet Handlers
// ===============================================================================================================
public void handlePlayerSyncPacket(PacketPlayerSync.Message message){
}
public void handlePlayerSyncPacket(PacketPlayerSync.Message message){}
public void handleGlyphDataPacket(PacketGlyphData.Message message){
}
public void handleGlyphDataPacket(PacketGlyphData.Message message){}
public void handleCastSpellPacket(PacketCastSpell.Message message){
}
public void handleEmitterDataPacket(PacketEmitterData.Message message){}
public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){
}
public void handleCastSpellPacket(PacketCastSpell.Message message){}
public void handleNPCCastSpellPacket(Message message){
}
public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){}
public void handleTransportationPacket(PacketTransportation.Message message){
}
public void handleNPCCastSpellPacket(PacketNPCCastSpell.Message message){}
public void handleClairvoyancePacket(PacketClairvoyance.Message message){
}
public void handleDispenserCastSpellPacket(PacketDispenserCastSpell.Message message){}
public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){}
public void handleTransportationPacket(PacketTransportation.Message message){}
public void handleClairvoyancePacket(PacketClairvoyance.Message message){}
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){}
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){}
public void handleResurrectionPacket(PacketResurrection.Message message){}
public void handlePossessionPacket(PacketPossession.Message message){}
public void handleConquerShrinePacket(PacketConquerShrine.Message message){}
// SECTION Misc
// ===============================================================================================================
public void setToNumberSliderEntry(Property property){
}
public void setToNumberSliderEntry(Property property){}
public void setToHUDChooserEntry(Property property){}
public void setToNamedBooleanEntry(Property property){}
// public void setToEntityNameEntry(Property property){}
/**
@@ -194,13 +163,82 @@ public class CommonProxy {
*
* @param entity The source of the sound
* @param sound The SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param repeat Whether to repeat the sound for as long as the entity is alive (or until stopped manually)
*/
public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){
public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){}
/**
* Plays a continuous spell sound which moves with the given entity.
*
* @param entity The source of the sound
* @param spell The spell this sound is associated with
* @param start The starting SoundEvent to play
* @param loop The main looped SoundEvent to play
* @param end The ending SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
*/
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){}
/**
* Plays a continuous spell sound which moves with the given entity.
*
* @param entity The source of the sound
* @param spell The spell this sound is associated with
* @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @throws IllegalArgumentException if the given array contains less than 3 sound events
*/
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch){
if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ "of sound events, but the given array contained less than 3 sound events!");
playSpellSoundLoop(entity, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch);
}
/**
* Plays a continuous spell sound at the given position.
* @param world The world in which to play the sound
* @param x The x coordinate to play the sound at
* @param y The y coordinate to play the sound at
* @param z The z coordinate to play the sound at
* @param spell The spell this sound is associated with
* @param start The starting SoundEvent to play
* @param loop The main looped SoundEvent to play
* @param end The ending SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
*/
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){}
/**
* Plays a continuous spell sound at the given position.
*
* @param world The world in which to play the sound
* @param x The x coordinate to play the sound at
* @param y The y coordinate to play the sound at
* @param z The z coordinate to play the sound at
* @param spell The spell this sound is associated with
* @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
* @throws IllegalArgumentException if the given array contains less than 3 sound events
*/
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch, int duration){
if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ "of sound events, but the given array contained less than 3 sound events!");
playSpellSoundLoop(world, x, y, z, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch, duration);
}
/**
* Gets the client side world using Minecraft.getMinecraft().world. <b>Only to be called client side!</b> Returns
* null on the server side.
@@ -209,4 +247,8 @@ public class CommonProxy {
return null;
}
/** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */
public Set<String> getSpellHUDSkins(){
return null;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,715 +0,0 @@
package electroblob.wizardry;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.enchantment.Imbuement;
import electroblob.wizardry.entity.EntityShield;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketPlayerSync;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.spell.None;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagInt;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
/**
* Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave minimum
* external changes (for my own sanity, mainly!). Turns out the only major difference between an internal capability and
* an IEEP is a couple of redundant classes and a different way of registering it.
* <p>
* Forge seems to have separate classes to hold the Capability<...> instance ('key') and methods for getting the
* capability, but in my opinion there are already too many classes to deal with, so I'm not adding any more than are
* necessary, meaning those constants and values are kept here instead.
*
* @since Wizardry 2.1
* @author Electroblob
*/
// On the plus side, having to rethink this class allowed me to clean it up a lot.
@Mod.EventBusSubscriber
public class WizardData implements INBTSerializable<NBTTagCompound> {
/** Static instance of what I like to refer to as the capability key. Private because, well, it's internal! */
// This annotation does some crazy Forge magic behind the scenes and assigns this field a value.
@CapabilityInject(WizardData.class)
private static final Capability<WizardData> WIZARD_DATA_CAPABILITY = null;
/** The player this WizardData instance belongs to. */
private final EntityPlayer player;
// This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry.
private Map<Imbuement, Integer> imbuementDurations;
public boolean hasSpiritWolf;
public boolean hasSpiritHorse;
/**
* Whether this player is currently casting a continuous spell via commands. Not saved over world reload and reset
* on player death.
*/
private Spell currentlyCasting;
/**
* The time for which this player has been casting a continuous spell via commands. Increments by 1 each tick. Not
* saved over world reload and reset on player death.
*/
private int castingTick;
/**
* SpellModifiers object for the current continuous spell cast via commands. Not saved over world reload and reset
* on player death.
*/
private SpellModifiers spellModifiers;
/** Coordinates for the saved transportation stone circle location. Will be null if no location is saved. */
private BlockPos stoneCircleLocation;
/** Dimension id which the saved stone circle is in. */
private int stoneCircleDimension;
/** Time left until the player teleports under the effect of transportation */
private int tpCountdown;
/** Coordinates for the saved clairvoyance location. Will be null if no location is saved. */
private BlockPos clairvoyanceLocation;
/** Dimension id which the saved clairvoyance point is in. */
private int clairvoyanceDimension;
public EntityShield shield;
public WeakReference<ISummonedCreature> selectedMinion;
/**
* Set of this player's discovered spells. <b>Do not write to this list directly</b>, use
* {@link WizardData#discoverSpell(Spell)} instead.
*/
public Set<Spell> spellsDiscovered;
private Set<UUID> allies;
/**
* List of usernames of this player's allies. May not be accurate 100% of the time. This is here so that a player
* can view the usernames of their allies even when those allies are not online. <b> Do not use this for any other
* purpose than displaying the names!
*/
public Set<String> allyNames;
private Set<UUID> soulboundCreatures;
public WizardData(){
this(null); // Nullary constructor for the registration method factory parameter
}
public WizardData(EntityPlayer player){
this.player = player;
this.imbuementDurations = new HashMap<Imbuement, Integer>();
this.spellsDiscovered = new HashSet<Spell>();
// All players can recognise magic missile. This is not done using discoverSpell because that seems to cause
// a crash on load occasionally (probably something to do with achievements being initalised)
this.spellsDiscovered.add(Spells.magic_missile);
this.hasSpiritWolf = false;
this.hasSpiritHorse = false;
this.currentlyCasting = Spells.none;
this.spellModifiers = new SpellModifiers();
this.castingTick = 0;
this.stoneCircleDimension = 0;
this.clairvoyanceDimension = 0;
this.setTpCountdown(0);
this.allies = new HashSet<UUID>();
this.allyNames = new HashSet<String>();
this.soulboundCreatures = new HashSet<UUID>();
}
public boolean hasSpellBeenDiscovered(Spell spell){
return spellsDiscovered.contains(spell) || spell instanceof None;
}
/**
* Adds the given spell to the list of discovered spells for this player. Automatically takes into account whether
* the spell has been discovered. Use this method rather than adding directly to the list because it handles
* achievements.
*
* @param spell The spell to be discovered
* @return True if the spell had not already been discovered; false otherwise.
*/
public boolean discoverSpell(Spell spell){
if(spellsDiscovered == null){
spellsDiscovered = new HashSet<Spell>();
}
// The 'none' spell cannot be discovered
if(spell instanceof None) return false;
// Tries to add the spell to the list of discovered spells, and returns false if it was already present
if(!spellsDiscovered.add(spell)) return false;
// If the spell had not already been discovered, achievements can be triggered and the method returns true
if(spellsDiscovered.containsAll(Spell.getSpells(Spell::isEnabled))){
WizardryAdvancementTriggers.all_spells.triggerFor(this.player);
}
for(Element element : Element.values()){
if(element != Element.MAGIC
&& spellsDiscovered.containsAll(Spell.getSpells(new Spell.TierElementFilter(null, element)))){
WizardryAdvancementTriggers.element_master.triggerFor(this.player);
}
}
return true;
}
/** Sets the player's saved transportation stone location and dimension. */
public void setStoneCircleLocation(BlockPos pos, int dimensionID){
this.stoneCircleLocation = pos;
this.stoneCircleDimension = dimensionID;
}
/** Returns the coordinates of the associated player's saved transportation stone circle. */
public BlockPos getStoneCircleLocation(){
return stoneCircleLocation;
}
/** Returns the dimension ID of the associated player's saved transportation stone circle. */
public int getStoneCircleDimension(){
return stoneCircleDimension;
}
public int getTpCountdown(){
return tpCountdown;
}
public void setTpCountdown(int tpCountdown){
this.tpCountdown = tpCountdown;
}
/** Sets the player's saved clairvoyance location. */
public void setClairvoyancePoint(BlockPos pos, int dimensionID){
this.clairvoyanceLocation = pos;
this.clairvoyanceDimension = dimensionID;
}
/** Returns the coordinates for the saved clairvoyance location. Will be null if no location is saved. */
public BlockPos getClairvoyanceLocation(){
return clairvoyanceLocation;
}
/** Returns the dimension ID for the saved clairvoyance location. Will be null if no location is saved. */
public int getClairvoyanceDimension(){
return clairvoyanceDimension;
}
/**
* Overwrites the imbuement duration associated with the given imubement for this player, or creates it if there was
* none previously.
*
* @throws IllegalArgumentException if the given {@link Enchantment} is not an {@link Imbuement}.
*/
public void setImbuementDuration(Enchantment enchantment, int duration){
// It is best to throw an exception here, because otherwise the error would either go unnoticed (if
// non-imbuements
// were ignored) or cause a ClassCastException later (if non-imbuements were allowed to be added).
if(enchantment instanceof Imbuement){
this.imbuementDurations.put((Imbuement)enchantment, duration);
}else{
throw new IllegalArgumentException(
"Attempted to set an imbuement duration for something that isn't an Imbuement! (This exception has been thrown now to prevent a ClassCastException from occurring later.)");
}
}
/**
* Returns the imbuement duration associated with the given imbuement for this player, or 0 if it does not exist.
*/
@SuppressWarnings("unlikely-arg-type")
public int getImbuementDuration(Enchantment enchantment){
// Need to check that i is not null, otherwise it throws an NPE when Java auto-unboxes it.
// What's nice here is that the map simply accepts objects as keys, so there's no need to cast or throw
// exceptions.
Integer i = this.imbuementDurations.get(enchantment);
// If i is null, returns 0; otherwise returns i, auto-unboxed to an int.
return i == null ? 0 : i;
}
/**
* Decrements the duration for each conjured item by 1, and removes from the map any that are 0 or less or that the
* player no longer has. Also deletes the item from the player's inventory if it runs out of time.
*/
private void updateImbuedItems(){
Set<Imbuement> activeImbuements = new HashSet<Imbuement>();
// For each item in the player's inventory
for(ItemStack stack : player.inventory.mainInventory){
if(stack.isItemEnchanted()){
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
Iterator<Entry<Enchantment, Integer>> iterator = enchantments.entrySet().iterator();
// For each of the item's enchantments
while(iterator.hasNext()){
Enchantment enchantment = iterator.next().getKey();
// Ignores the enchantment unless it is an imbuement
if(enchantment instanceof Imbuement){
int duration = this.getImbuementDuration(enchantment);
// If the imbuement is still active:
if(duration > 0){
// Decrements the timer
this.imbuementDurations.put((Imbuement)enchantment, duration - 1);
// Adds this imbuement to the set of imbuements that need to be kept
activeImbuements.add((Imbuement)enchantment);
// Otherwise:
}else{
// Removes the enchantment from the enchantment map
iterator.remove();
// Applies the new enchantment map to the item
EnchantmentHelper.setEnchantments(enchantments, stack);
}
}
}
}
}
// Removes all imbuements from the map that are no longer active
this.imbuementDurations.keySet().retainAll(activeImbuements);
}
/**
* Adds the given player to the list of allies belonging to the associated player, or removes the player if they are
* already in the list of allies. Returns true if the player was added, false if they were removed.
*/
public boolean toggleAlly(EntityPlayer player){
if(this.isPlayerAlly(player)){
this.allies.remove(player.getUniqueID());
// The remove method uses .equals() rather than == so this will work fine.
this.allyNames.remove(player.getName());
return false;
}else{
this.allies.add(player.getUniqueID());
this.allyNames.add(player.getName());
return true;
}
}
/** Returns whether the given player is in this player's list of allies, or is on the same team as this player. */
public boolean isPlayerAlly(EntityPlayer player){
return this.allies.contains(player.getUniqueID()) || this.player.isOnSameTeam(player);
}
/** Adds the given entity to this player's list of soulbound creatures, and returns whether it succeeded. */
public boolean soulbind(EntityLivingBase target){
return this.soulboundCreatures.add(target.getUniqueID());
}
/** Returns whether the given entity has been soulbound to this player. */
public boolean isCreatureSoulbound(EntityPlayer target){
return this.soulboundCreatures.contains(target.getUniqueID());
}
/**
* Damages all creatures soulbound to this player by the given amount, and removes from the list any that no longer
* exist.
*/
public void damageAllSoulboundCreatures(float damage){
for(Iterator<UUID> iterator = this.soulboundCreatures.iterator(); iterator.hasNext();){
Entity entity = WizardryUtilities.getEntityByUUID(this.player.world, iterator.next());
if(entity == null) iterator.remove();
if(entity instanceof EntityLivingBase){
// Retaliatory effect
if(entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(this.player, DamageType.MAGIC, true),
damage)){
// Sound only plays if the damage succeeds
player.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, player.world.rand.nextFloat() * 0.2F + 1.0F);
}
}
}
}
/** Starts casting the given spell with the given modifiers. */
public void startCastingContinuousSpell(Spell spell, SpellModifiers modifiers){
this.currentlyCasting = spell;
this.spellModifiers = modifiers;
if(!this.player.world.isRemote){
PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player.getEntityId(),
spell.id(), this.spellModifiers);
WizardryPacketHandler.net.sendToDimension(message, this.player.world.provider.getDimension());
}
}
/** Stops casting the current spell. */
public void stopCastingContinuousSpell(){
this.currentlyCasting = Spells.none;
this.castingTick = 0;
this.spellModifiers.reset();
if(!this.player.world.isRemote){
PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player.getEntityId(),
Spells.none.id(), this.spellModifiers);
WizardryPacketHandler.net.sendToDimension(message, this.player.world.provider.getDimension());
}
}
/** Casts the current continuous spell, fires relevant events and updates the castingTick field. */
public void updateContinuousSpellCasting(){
if(this.currentlyCasting != null && this.currentlyCasting.isContinuous){
if(MinecraftForge.EVENT_BUS.post(
new SpellCastEvent.Tick(player, currentlyCasting, spellModifiers, Source.COMMAND, castingTick))){
this.stopCastingContinuousSpell();
return;
}
if(this.currentlyCasting.cast(player.world, player, EnumHand.MAIN_HAND, castingTick, this.spellModifiers)
&& this.castingTick == 0){
// On the first tick casting a continuous spell via commands, SpellCastEvent.Post is fired.
MinecraftForge.EVENT_BUS
.post(new SpellCastEvent.Post(player, currentlyCasting, spellModifiers, Source.COMMAND));
}
castingTick++;
}else{
// Why is this here? Surely castingTick will always be 0 if currentlyCasting is null?
this.castingTick = 0;
}
}
/** Returns whether this player is currently casting a continuous spell via commands. */
public boolean isCasting(){
return this.currentlyCasting != null && this.currentlyCasting != Spells.none;
}
/**
* Returns the continuous spell this player is currently casting via commands, or the 'none' spell if they aren't
* casting anything.
*/
public Spell currentlyCasting(){
return currentlyCasting;
}
/** Called each time the associated player is updated. */
private void update(){
if(this.selectedMinion != null && this.selectedMinion.get() == null) this.selectedMinion = null;
// This new system removes a lot of repetitive event handler code and inflexible variables which had duplicate
// functions, just for different enchantments.
updateImbuedItems();
if(!player.world.isRemote){
if(getTpCountdown() == 1){
player.setPositionAndUpdate(this.stoneCircleLocation.getX() + 0.5, this.stoneCircleLocation.getY(),
this.stoneCircleLocation.getZ() + 0.5);
player.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 50, 0));
IMessage msg = new PacketTransportation.Message(player.getEntityId());
WizardryPacketHandler.net.sendToDimension(msg, player.world.provider.getDimension());
}
if(getTpCountdown() > 0){
setTpCountdown(getTpCountdown() - 1);
}
}
updateContinuousSpellCasting();
}
/**
* Returns the WizardData instance for the specified player.
*/
public static final WizardData get(EntityPlayer player){
return player.getCapability(WIZARD_DATA_CAPABILITY, null);
}
/**
* Called from the event handler each time the associated player entity is cloned, i.e. on respawn or when
* travelling to a different dimension. Used to copy over any variables that should persist over player death. This
* is the inverse of the old onPlayerDeath method, which reset the variables that shouldn't persist.
*
* @param data The old WizardData whose variables are to be copied over.
* @param respawn True if the player died and is respawning, false if they are just travelling between dimensions.
*/
public void copyFrom(WizardData data, boolean respawn){
// TODO: What happens with spirit wolf and spirit horse?
this.hasSpiritHorse = data.hasSpiritHorse;
this.hasSpiritWolf = data.hasSpiritWolf;
this.allies = data.allies;
this.allyNames = data.allyNames;
this.clairvoyanceDimension = data.clairvoyanceDimension;
this.clairvoyanceLocation = data.clairvoyanceLocation;
this.selectedMinion = data.selectedMinion;
// Curse of soulbinding is lifted when the caster dies, but not when they switch dimensions.
if(!respawn) this.soulboundCreatures = data.soulboundCreatures;
this.spellsDiscovered = data.spellsDiscovered;
this.stoneCircleDimension = data.stoneCircleDimension;
this.stoneCircleLocation = data.stoneCircleLocation;
// Imbuements are lost on death so their durations do not persist.
// Command spell casting is reset on death so the associated variables do not persist.
// tpCountdown is reset both when the player dies and when they switch dimensions.
}
/** Sends a packet to this player's client to synchronise necessary information. Only called server side. */
public void sync(){
if(this.player instanceof EntityPlayerMP){
int id = -1;
if(this.selectedMinion != null && this.selectedMinion.get() instanceof Entity)
id = ((Entity)this.selectedMinion.get()).getEntityId();
IMessage msg = new PacketPlayerSync.Message(this.spellsDiscovered, id);
WizardryPacketHandler.net.sendTo(msg, (EntityPlayerMP)this.player);
}
}
@Override
public NBTTagCompound serializeNBT(){
NBTTagCompound properties = new NBTTagCompound();
// ...so Java 8 allows you to do stuff like this:
properties.setTag("imbuements", WizardryUtilities.mapToNBT(this.imbuementDurations,
imbuement -> new NBTTagInt(Enchantment.getEnchantmentID((Enchantment)imbuement)), NBTTagInt::new));
properties.setBoolean("hasSpiritWolf", this.hasSpiritWolf);
properties.setBoolean("hasSpiritHorse", this.hasSpiritHorse);
if(this.stoneCircleLocation != null)
properties.setLong("stoneCircleLocation", this.stoneCircleLocation.toLong());
properties.setInteger("stoneCircleDimension", this.stoneCircleDimension);
properties.setInteger("tpCountdown", this.tpCountdown);
if(this.clairvoyanceLocation != null)
properties.setLong("clairvoyanceLocation", this.clairvoyanceLocation.toLong());
properties.setInteger("clairvoyanceDimension", this.getClairvoyanceDimension());
// THIS is why I wrote the list/map <-> NBT methods. Look how neat this is!
properties.setTag("allies", WizardryUtilities.listToNBT(this.allies, WizardryUtilities::UUIDtoTagCompound));
properties.setTag("allyNames", WizardryUtilities.listToNBT(this.allyNames, NBTTagString::new));
properties.setTag("soulboundCreatures",
WizardryUtilities.listToNBT(this.soulboundCreatures, WizardryUtilities::UUIDtoTagCompound));
// Might be worth converting this over to WizardryUtilities.listToNBT.
int[] spells = new int[this.spellsDiscovered.size()];
int i = 0;
for(Spell spell : this.spellsDiscovered){
spells[i] = spell.id();
i++;
}
properties.setIntArray("discoveredSpells", spells);
return properties;
}
@Override
public void deserializeNBT(NBTTagCompound nbt){
if(nbt != null){
this.imbuementDurations = WizardryUtilities.NBTToMap(nbt.getTagList("imbuements", NBT.TAG_COMPOUND),
(NBTTagInt tag) -> (Imbuement)Enchantment.getEnchantmentByID(tag.getInt()), NBTTagInt::getInt);
this.hasSpiritWolf = nbt.getBoolean("hasSpiritWolf");
this.hasSpiritHorse = nbt.getBoolean("hasSpiritHorse");
this.stoneCircleLocation = BlockPos.fromLong(nbt.getLong("stoneCircleLocation"));
this.stoneCircleDimension = nbt.getInteger("stoneCircleDimension");
this.tpCountdown = nbt.getInteger("tpCountdown");
this.clairvoyanceLocation = BlockPos.fromLong(nbt.getLong("clairvoyanceLocation"));
this.clairvoyanceDimension = nbt.getInteger("clairvoyanceDimension");
this.allies = new HashSet<UUID>(WizardryUtilities.NBTToList(nbt.getTagList("allies", NBT.TAG_COMPOUND),
WizardryUtilities::tagCompoundToUUID));
this.allyNames = new HashSet<String>(
WizardryUtilities.NBTToList(nbt.getTagList("allyNames", NBT.TAG_STRING), NBTTagString::getString));
this.soulboundCreatures = new HashSet<UUID>(WizardryUtilities.NBTToList(
nbt.getTagList("soulboundCreatures", NBT.TAG_COMPOUND), WizardryUtilities::tagCompoundToUUID));
this.spellsDiscovered = new HashSet<Spell>();
for(int id : nbt.getIntArray("discoveredSpells")){
spellsDiscovered.add(Spell.get(id));
}
}
}
// Event handlers
@SubscribeEvent
// The type parameter here has to be Entity, not EntityPlayer, or the event won't get fired.
public static void onCapabilityLoad(AttachCapabilitiesEvent<Entity> event){
if(event.getObject() instanceof EntityPlayer)
event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"),
new WizardData.Provider((EntityPlayer)event.getObject()));
// This demonstrates why capabilities are badly structured: The following code compiles, but what it does is put
// a player into a CapabilityDispatcher, which is in turn stored in that very same player, which makes no sense
// at all!
// event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), event.getObject());
}
@SubscribeEvent
public static void onPlayerCloneEvent(PlayerEvent.Clone event){
WizardData newData = WizardData.get(event.getEntityPlayer());
WizardData oldData = WizardData.get(event.getOriginal());
newData.copyFrom(oldData, event.isWasDeath());
}
@SubscribeEvent
public static void onEntityJoinWorld(EntityJoinWorldEvent event){
if(!event.getEntity().world.isRemote && event.getEntity() instanceof EntityPlayerMP){
// Synchronises wizard data after loading.
WizardData data = WizardData.get((EntityPlayer)event.getEntity());
if(data != null) data.sync();
}
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
if(WizardData.get(player) != null){
WizardData.get(player).update();
}
}
}
/**
* This is a nested class for a few reasons: firstly, it makes sense because instances of this and WizardData go
* hand-in-hand; secondly, it's too short to be worth a separate file; and thirdly (and most importantly) it allows
* me to access WIZARD_DATA_CAPABILITY while keeping it private.
*/
public static class Provider implements ICapabilitySerializable<NBTTagCompound> {
private final WizardData data;
public Provider(EntityPlayer player){
data = new WizardData(player);
}
@Override
public boolean hasCapability(Capability<?> capability, EnumFacing facing){
return capability == WIZARD_DATA_CAPABILITY;
}
@Override
public <T> T getCapability(Capability<T> capability, EnumFacing facing){
if(capability == WIZARD_DATA_CAPABILITY){
return WIZARD_DATA_CAPABILITY.cast(data);
}
return null;
}
@Override
public NBTTagCompound serializeNBT(){
return data.serializeNBT();
}
@Override
public void deserializeNBT(NBTTagCompound nbt){
data.deserializeNBT(nbt);
}
}
// Ended up deleting IWizardData because it was unnecessary. This is the comment that was at the start of it:
/* I'm not going to lie, I will never find the capabilities system even remotely intuitive so this is a bare-minimum
* approach just to get things working (four classes where one would have done?!) At one point I considered simply
* wrapping my old IEEP inside a single-field capability, but I eventually decided I would at least *try* to do it
* properly.
*
* "...without having to directly implement many interfaces." - Forge Docs. I still can't see what's wrong with
* implementing many interfaces; surely that's what Java interfaces are designed for?
*
* Other things I find annoying: - IStorage. It's completely redundant in the majority of cases, and I don't
* understand why we need yet another separate class. - Making an interface, only to implement it once and once
* only. This completely defeats the point of interfaces. - The EnumFacing parameter, which is again redundant for
* everything that isn't a tile entity. So much for a clean, neat system.
*
* What Forge has effectively done is conflated two different functions: attaching data to stuff and cross-mod
* integration/soft dependencies. I think this is bad design; it would have been better to keep the two features
* separate.
*
* Here's my current understanding of how the capability system works: - You make an interface which defines the
* things your capability can do (this class). I will call this the TEMPLATE. - You implement that interface with
* your default implementation (WizardData). This is the closest analog to your old IEEP implementation class. THIS
* CLASS STORES ALL THE VARIABLES, and hence has one instance for each instance of whatever it is attached to. I
* will call this the DATA. - The DATA class implements INBTSerializable (assuming you want it to be saved, which is
* nearly always the case) - Despite its name, Capability<T> does NOT represent a capability itself. Instead, it
* acts as a sort of identifier/key, the idea being that you can access a particular instance of your DATA given the
* key (which tells forge that you want a capability of type TEMPLATE) and the object you want the DATA for. This is
* what Entity.getCapability(...) does.
*
* To really understand what's going on though, you need to sift through Forge's verbose data structures and find
* where capabilities are actually hooked into vanilla: - Anything that implements ICapabilityProvider will have a
* private CapabilityDispatcher field. This holds other ICapabilityProviders. (I know. This inheritance pattern DOES
* NOT MAKE SENSE, because these could, in theory, be OTHER ENTITIES!) - This field is assigned a value through
* Forge's event factory, which, as we are all familiar with, calls all the methods marked with @SubscribeEvent.
* These methods add individual ICapabilityProviders to a Map stored in the event, which the event factory then
* wraps in a CapabilityDispatcher (which is itself an ICapabilityProvider) for the object that called it. - In your
* event handler, you return a custom ICapabilityProvider which is effectively bolted on to the player, and
* duplicates the ICapabilityProvider methods so you can hook into them and return an instance of your DATA class. -
* Where before there was a simple collection of IEEPs stored in the player, there is now a tree of
* ICapabilityProviders:
*
* - Entity/TileEntity/ItemStack - Vanilla ICapabilityProviders, mostly IItemHandlers, stored as fields. -
* CapabilityDispatcher, stored as a field. - Custom ICapabilityProviders - Custom CapabilityDispatchers - ...
*
* Most importantly, EACH PLAYER HOLDS THEIR OWN INSTANCE OF THIS TREE.
*
* When a capability is retrieved, the following process happens: 1. For the Entity/TileEntity/ItemStack instance,
* ICapabilityProvider.getCapability(...) is called. 2. The request propogates through the tree and finds the
* requested capability. */
}
+103 -99
View File
@@ -1,23 +1,22 @@
package electroblob.wizardry;
import org.apache.logging.log4j.Logger;
import electroblob.wizardry.command.CommandCastSpell;
import electroblob.wizardry.command.CommandDiscoverSpell;
import electroblob.wizardry.command.CommandSetAlly;
import electroblob.wizardry.command.CommandViewAllies;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration;
import electroblob.wizardry.misc.Forfeit;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryRegistry;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.CustomSoundCategory;
import electroblob.wizardry.util.SpellProperties;
import electroblob.wizardry.worldgen.*;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTBase;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.Capability.IStorage;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
@@ -30,73 +29,59 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry;
import org.apache.logging.log4j.Logger;
/**
* <i>"Electroblob's Wizardry adds an RPG-like system of spells to Minecraft, with the aim of being as playable as
* possible. No crazy constructs, no perk trees, no complex recipes - simply find spell books, cast spells, and master
* the arcane! - But you knew that, right?"</i>
* <p></p>
* Main mod class for Wizardry. Contains the logger and settings instances, along with all the other stuff that's normally
* in a main mod class.
* @author Electroblob
* @since Wizardry 1.0
*/
@Mod(modid = Wizardry.MODID, name = Wizardry.NAME, version = Wizardry.VERSION, guiFactory = "electroblob.wizardry.WizardryGuiFactory")
public class Wizardry {
/** Wizardry's mod ID. */
// This is going to have to change for 1.12 or it'll conflict with the other wizardry mod.
// They were there first, it's only fair... although I wonder if that will have unintended side-effects?
public static final String MODID = "ebwizardry";
/** Wizardry's mod name, in readable form. */
public static final String NAME = "Electroblob's Wizardry";
/**
* The version number for this version of wizardry. The following system is used for version numbers:
* <p>
* <p></p>
* <center><b>[major Minecraft version].[major mod version].[minor mod version/patch]
* <p>
* <p></p>
* </center></b> The major mod version is consistent across Minecraft versions, i.e. Wizardry 1.1 has the same
* features as Wizardry 2.1, but they are for different versions of Minecraft and have separate minor versioning.
* 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.1.0";
public static final String VERSION = "4.2.0";
// IDEA: Improve the algorithm that finds a place to summon creatures to take walls into account.
// IDEA: Replace all uses of Math.cos and Math.sin with MathHelper versions
// IDEA: Triggering of inbuilt Forge events in relevant places?
/* Minor bugs that need fixing at some point:
* - Player skin hat layer shows through wizard hats - Wizard armour breaks rather than just running out of mana (I
* can't seem to replicate this bug, but I have had it happen to me before...)
* - Shift-clicking a stack of special upgrades when in the arcane workbench causes the whole stack to be
* transferred when it should be just one (this is a bug with vanilla as well - try putting a stack of bottles into
* a brewing stand). I have at least made it so only one gets used now, so it has no impact on the game.
* - When a spell is on cooldown, you can't break blocks when holding a wand. */
// TODO: So somehow I have managed to overlook the fact that health is actually a float. What this means is that
// I can make the healing spells use damage multipliers - hurrah!
// TODO: Switch from IInventory to IItemHandler (Or don't. It's only useful for automation really.)
// TODO: Rearrange the config gui and file
// Updating:
// TODO: Make sure all obf reflection names are still correct
// TODO: Go through each of the summoned creatures and check there are no extra vanilla methods to override
// TODO: Check whether Minecraft has added some methods that do the same job as my utilities, particularly in
// relation to itemstacks, hotbar, armour slots and inventory slots.
// TODO: Add wizard towers to the /locate command
// TODO: Fix cascading worldgen lag and backport (offset area into loaded chunks, possibly use WorldGenMinable)
// TODO: Change evil wizard sounds again to use illager sounds?
// TODO: Chnage all instanceof checks on split entity variant classes (zombie, skeleton, etc.) to their abstract
// base classes where appropriate
// IDEA: Abstract the vanilla particles behind the particle builder
// TODO: Have particles obey Minecraft's particle setting where appropriate
// (see https://github.com/RootsTeam/Embers/blob/master/src/main/java/teamroots/embers/particle/ParticleUtil.java)
// NOTE: Add melee upgrades to loot tables when they are added.
// TODO: TileEntityArcaneWorkbench needs looking at, esp. regarding inventory and markDirty
// TODO: See what can be done with the illager spell sounds (and go over sounds in general)
// TODO: Fix possession
// TODO: Fix charge
/** Static instance of the {@link Settings} object for Wizardry. */
public static final Settings settings = new Settings();
/** Static instance of the {@link Logger} object for Wizardry. */
/** Static instance of the {@link Logger} object for Wizardry.
* <p></p>
* Logging conventions for wizardry (only these levels are used currently):
* <p></p>
* - <b>ERROR</b>: Anything that threw an exception; may or may not crash the game.<br>
* - <b>WARN</b>: Anything that isn't supposed to happen during normal operation, but didn't throw an exception.<br>
* - <b>INFO</b>: Anything that might happen during normal mod operation that the user needs to know about. */
public static Logger logger;
// private static Pattern entityNamePattern;
// EventManager
WizardryWorldGenerator generator = new WizardryWorldGenerator();
// The instance of wizardry that Forge uses.
@Instance(Wizardry.MODID)
public static Wizardry instance;
@@ -109,39 +94,28 @@ public class Wizardry {
public void preInit(FMLPreInitializationEvent event){
logger = event.getModLog();
proxy.registerResourceReloadListeners();
settings.initConfig(event);
// Yes - by the looks of it, having an interface is completely unnecessary in this case.
CapabilityManager.INSTANCE.register(WizardData.class, new IStorage<WizardData>(){
// These methods are only called by Capability.writeNBT() or Capability.readNBT(), which in turn are
// NEVER CALLED. Unless I'm missing some reflective invocation, that means this entire class serves only
// to allow capabilities to be saved and loaded manually. What that would be useful for I don't know.
// (If an API forces most users to write redundant code for no reason, it's not user friendly, is it?)
// ... well, that's my rant for today!
@Override
public NBTBase writeNBT(Capability<WizardData> capability, WizardData instance, EnumFacing side){
return null;
}
@Override
public void readNBT(Capability<WizardData> capability, WizardData instance, EnumFacing side, NBTBase nbt){
}
}, WizardData::new);
WizardryRegistry.registerTileEntities();
WizardryRegistry.registerEntities();
// The check for the generateLoot setting is now done within this method.
WizardryRegistry.registerLoot();
// Capabilities
WizardData.register();
DispenserCastingData.register();
// Register things that don't have registries
WizardryBlocks.registerTileEntities();
WizardryLoot.register();
WizardryAdvancementTriggers.register();
Forfeit.register();
// Moved to preInit, because apparently it has to be here now.
// Client-side stuff (via proxies)
proxy.registerRenderers();
// It seems this also has to be here
proxy.registerKeyBindings();
WizardryBaublesIntegration.init();
WizardryAntiqueAtlasIntegration.init();
}
@EventHandler
@@ -149,27 +123,41 @@ public class Wizardry {
settings.initConfigExtras();
// Event Handlers
GameRegistry.registerWorldGenerator(generator, 0);
MinecraftForge.EVENT_BUS.register(new WizardryKeyHandler());
MinecraftForge.EVENT_BUS.register(instance);
proxy.registerSpellHUD(); // This can't easily be converted to use the new @Mod.EventBusSubscriber system
// 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
// Examples I've managed to find:
// - Tinker's construct slime islands use 25
GameRegistry.registerWorldGenerator(new WorldGenCrystalOre(), 0);
GameRegistry.registerWorldGenerator(new WorldGenCrystalFlower(), 50);
GameRegistry.registerWorldGenerator(new WorldGenWizardTower(), 20);
GameRegistry.registerWorldGenerator(new WorldGenObelisk(), 20);
GameRegistry.registerWorldGenerator(new WorldGenShrine(), 20);
// This is for the config change and missing mappings events
MinecraftForge.EVENT_BUS.register(instance); // Since there's already an instance we might as well use it
NetworkRegistry.INSTANCE.registerGuiHandler(this, new WizardryGuiHandler());
WizardryPacketHandler.initPackets();
// Post-registry extras
WizardryItems.populateWandMap();
WizardryItems.populateArmourMap();
WizardryItems.registerDispenseBehaviours();
Spell.registry.forEach(Spell::init);
SpellProperties.init();
// Client-side stuff (via proxies)
proxy.initGuiBits();
proxy.registerParticles();
proxy.registerSoundEventListener();
WizardrySounds.SPELLS = CustomSoundCategory.add(Wizardry.MODID + ":spells");
}
@EventHandler
public void postInit(FMLPostInitializationEvent event){
// This needs to be here or it won't necessarily include all the mods' entities.
// TODO: Re-implement this when the Forge bug is fixed.
/* Doesn't seem to be doing anything... String entityNames = ""; for(Object name :
* EntityList.classToStringMapping.values()){ if(name instanceof String){ entityNames = entityNames + name +
* '|'; } } // Cuts off the last '|' entityNames = entityNames.substring(0, entityNames.length()-1);
* entityNamePattern = Pattern.compile(entityNames); */
proxy.initialiseLayers();
WizardryTabs.sort();
}
@EventHandler
@@ -190,24 +178,31 @@ public class Wizardry {
// 2.1 changed some item ids, so this fixes them for existing worlds
// Nobody updates minecraft versions when using mods, but I may as well leave this here just in case.
@EventHandler
public static void onMissingMappingEvent(RegistryEvent.MissingMappings<Item> event){
@SubscribeEvent
public static void onMissingItemMappingEvent(RegistryEvent.MissingMappings<Item> event){
// Just get, not getAll, since the mod id didn't change!
for(RegistryEvent.MissingMappings.Mapping<Item> mapping : event.getAllMappings()){
if(mapping.key.getResourceDomain().equals(Wizardry.MODID)){
if(mapping.key.getNamespace().equals(Wizardry.MODID)){
Item replacement = null;
Item replacement;
switch(mapping.key.getResourcePath()){
switch(mapping.key.getPath()){
case "wand_basic": replacement = WizardryItems.magic_wand; break;
case "wand_basic_fire": replacement = WizardryItems.basic_fire_wand; break;
case "wand_basic_ice": replacement = WizardryItems.basic_ice_wand; break;
case "wand_basic_lightning": replacement = WizardryItems.basic_lightning_wand; break;
case "wand_basic_necromancy": replacement = WizardryItems.basic_necromancy_wand; break;
case "wand_basic_earth": replacement = WizardryItems.basic_earth_wand; break;
case "wand_basic_sorcery": replacement = WizardryItems.basic_sorcery_wand; break;
case "wand_basic_healing": replacement = WizardryItems.basic_healing_wand; break;
case "wand_basic_fire": replacement = WizardryItems.novice_fire_wand; break;
case "wand_basic_ice": replacement = WizardryItems.novice_ice_wand; break;
case "wand_basic_lightning": replacement = WizardryItems.novice_lightning_wand; break;
case "wand_basic_necromancy": replacement = WizardryItems.novice_necromancy_wand; break;
case "wand_basic_earth": replacement = WizardryItems.novice_earth_wand; break;
case "wand_basic_sorcery": replacement = WizardryItems.novice_sorcery_wand; break;
case "wand_basic_healing": replacement = WizardryItems.novice_healing_wand; break;
case "basic_fire_wand": replacement = WizardryItems.novice_fire_wand; break;
case "basic_ice_wand": replacement = WizardryItems.novice_ice_wand; break;
case "basic_lightning_wand": replacement = WizardryItems.novice_lightning_wand; break;
case "basic_necromancy_wand": replacement = WizardryItems.novice_necromancy_wand; break;
case "basic_earth_wand": replacement = WizardryItems.novice_earth_wand; break;
case "basic_sorcery_wand": replacement = WizardryItems.novice_sorcery_wand; break;
case "basic_healing_wand": replacement = WizardryItems.novice_healing_wand; break;
case "wand_apprentice": replacement = WizardryItems.apprentice_wand; break;
case "wand_apprentice_fire": replacement = WizardryItems.apprentice_fire_wand; break;
case "wand_apprentice_ice": replacement = WizardryItems.apprentice_ice_wand; break;
@@ -250,4 +245,13 @@ public class Wizardry {
}
}
@SubscribeEvent
public static void onMissingSpellMappingEvent(RegistryEvent.MissingMappings<Spell> event){
for(RegistryEvent.MissingMappings.Mapping<Spell> mapping : event.getAllMappings()){
if(mapping.key.getNamespace().equals(Wizardry.MODID)){
if(mapping.key.getPath().equals("firestorm")) mapping.remap(Spells.fire_breath);
}
}
}
}
@@ -1,118 +1,137 @@
package electroblob.wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.entity.EntityArc;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ItemWizardArmour;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.item.IManaStoringItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketSyncAdvancements;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.FreezingWeapon;
import electroblob.wizardry.spell.ImbueWeapon;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryUtilities;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.advancements.Advancement;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ContainerPlayer;
import net.minecraft.inventory.ContainerWorkbench;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraft.world.storage.loot.LootEntry;
import net.minecraft.world.storage.loot.LootEntryTable;
import net.minecraft.world.storage.loot.LootPool;
import net.minecraft.world.storage.loot.RandomValueRange;
import net.minecraft.world.storage.loot.conditions.LootCondition;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.entity.PlaySoundAtEntityEvent;
import net.minecraftforge.event.entity.living.*;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.player.AdvancementEvent;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
import java.util.ArrayList;
/**
* As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only a few
* miscellaneous things that don't make much sense anywhere else, or that are better kept together. Previously, this was
* a gigantic class with about half of the entire mod's logic in it!
* General-purpose event handler for things that don't fit anywhere else or groups of related behaviours that are better
* kept together. As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only
* a few miscellaneous things that don't make much sense anywhere else, or that are better kept together (previously,
* this was a gigantic class with about half of the entire mod's logic in it!)
*
* @author Electroblob
* @since Wizardry 1.0
*/
// The general rules for where event-based logic goes are:
// - If the logic relates only to vanilla things or is general (e.g. gameplay settings) it lives in here
// - If there is an obvious class for the thing relevant to the logic being performed, it goes in there for the sake
// of modularity/separation-of-concerns (e.g. armour cost reductions go in ItemWizardArmour)
// - If the thing has an instance but not a separate class (e.g. most custom potions), it goes in a related class
// where applicable (e.g. a spell class), or if not it goes in here
// - If several things share a significant amount of logic, to avoid duplicate code and potentially improve efficiency
// they should be kept together either in a class relevant to all of them (probably a common superclass) or in here
// - Client-side logic goes in WizardryClientEventHandler or another relevant client-side class
@Mod.EventBusSubscriber
public final class WizardryEventHandler {
// IDEA: Config option allowing users to specify loot locations
private static final String[] LOOT_INJECTION_LOCATIONS = {"minecraft:chests/simple_dungeon",
"minecraft:chests/abandoned_mineshaft", "minecraft:chests/desert_pyramid", "minecraft:chests/jungle_temple",
"minecraft:chests/stronghold_corridor", "minecraft:chests/stronghold_crossing",
"minecraft:chests/stronghold_library", "minecraft:chests/igloo_chest", "minecraft:chests/woodland_mansion",
"minecraft:chests/end_city_treasure"};
@SubscribeEvent
public static void onLootTableLoadEvent(LootTableLoadEvent event){
if(Wizardry.settings.generateLoot){
for(String location : LOOT_INJECTION_LOCATIONS){
if(event.getName().toString().matches(location)){
event.getTable().addPool(getAdditive(Wizardry.MODID + ":chests/dungeon_additions"));
}
}
}
}
private static LootPool getAdditive(String entryName){
return new LootPool(new LootEntry[]{getAdditiveEntry(entryName, 1)}, new LootCondition[0],
new RandomValueRange(1), new RandomValueRange(0, 1), Wizardry.MODID + "_additive_pool");
}
private static LootEntryTable getAdditiveEntry(String name, int weight){
return new LootEntryTable(new ResourceLocation(name), weight, 0, new LootCondition[0],
Wizardry.MODID + "_additive_entry");
}
private WizardryEventHandler(){} // No instances!
@SubscribeEvent
public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){
// When a player logs in, they are sent the glyph data and the server's settings.
// When a player logs in, they are sent the glyph data, server settings and spell properties.
if(event.player instanceof EntityPlayerMP){
SpellGlyphData.get(event.player.world).sync((EntityPlayerMP)event.player);
SpellEmitterData.get(event.player.world).sync((EntityPlayerMP)event.player);
Wizardry.settings.sync((EntityPlayerMP)event.player);
syncAdvancements((EntityPlayerMP)event.player, false);
}
Spell.syncProperties(event.player);
}
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onPlaySoundAtEntityEvent(PlaySoundAtEntityEvent event){
// Muffle (there's no spell class for it so it's here instead)
if(event.getEntity() instanceof EntityLivingBase
&& ((EntityLivingBase)event.getEntity()).isPotionActive(WizardryPotions.muffle)){
event.setCanceled(true);
}
}
@SubscribeEvent
public static void onAdvancementEvent(AdvancementEvent event){
// Forge has no hook for revoked advancements :(
// Guess we'll just have to make do
// Also, this seems to get fired on player login, so to prevent the toasts from appearing every login the
// only way I can see to do it is by testing the player has been around long enough.
if(event.getEntityPlayer() instanceof EntityPlayerMP && event.getEntityPlayer().ticksExisted > 0){
syncAdvancements((EntityPlayerMP)event.getEntityPlayer(), true);
}
}
private static void syncAdvancements(EntityPlayerMP player, boolean showToasts){
Wizardry.logger.info("Synchronising advancements for " + player.getName());
ArrayList<ResourceLocation> advancements = new ArrayList<>();
for(Advancement advancement : player.getServer().getAdvancementManager().getAdvancements()){
if(player.getAdvancements().getProgress(advancement).isDone()) advancements.add(advancement.getId());
}
WizardryPacketHandler.net.sendTo(new PacketSyncAdvancements.Message(showToasts, advancements.toArray(new ResourceLocation[0])), player);
}
@SubscribeEvent(priority = EventPriority.HIGH) // Disabling of specific spells comes after arcane jammer but before everything else
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
boolean enabled = true;
switch(event.getSource()){
case WAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.WANDS); break;
case SCROLL: enabled = event.getSpell().isEnabled(SpellProperties.Context.SCROLL); break;
case COMMAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.COMMANDS); break;
case NPC: enabled = event.getSpell().isEnabled(SpellProperties.Context.NPCS); break;
case DISPENSER: enabled = event.getSpell().isEnabled(SpellProperties.Context.DISPENSERS); break;
case OTHER: enabled = event.getSpell().isEnabled(); break; // Any enabled context will do for this one
}
// If a spell is disabled in the config, it will not work.
if(!event.getSpell().isEnabled()){
if(!event.getEntityLiving().world.isRemote) event.getEntity().sendMessage(
if(!enabled){
if(event.getCaster() != null && !event.getCaster().world.isRemote) event.getCaster().sendMessage(
new TextComponentTranslation("spell.disabled", event.getSpell().getNameForTranslationFormatted()));
event.setCanceled(true);
}
@@ -121,32 +140,36 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onSpellCastPostEvent(SpellCastEvent.Post event){
// Spell discovery (only players can discover spells, obviously)
if(event.getEntity() instanceof EntityPlayer){
if(event.getCaster() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntity();
EntityPlayer player = (EntityPlayer)event.getCaster();
// Advancement triggers
if(player instanceof EntityPlayerMP){
WizardryAdvancementTriggers.cast_spell.trigger((EntityPlayerMP)player, event.getSpell(), player.getHeldItem(player.getActiveHand()));
}
// Spell discovery (only players can discover spells, obviously)
WizardData data = WizardData.get(player);
if(data != null){
// Data is updated on both sides (This line was added client-side to fix a bug back in 1.1.3, so now
// it's in common code, which is nice!)
// Short-circuiting AND means that discoverSpell is only called if the event isn't cancelled.
if(!MinecraftForge.EVENT_BUS
.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
&& data.discoverSpell(event.getSpell())){
// If the spell wasn't already discovered, other stuff happens:
if(event.getSource() == SpellCastEvent.Source.COMMAND){
// If the spell didn't send a packet itself, the extended player needs to be synced so the
// spell discovery updates on the client.
if(!event.getSpell().doesSpellRequirePacket()) data.sync();
if(!event.getSpell().requiresPacket()) data.sync();
}else if(!event.getEntity().world.isRemote && !player.capabilities.isCreativeMode
}else if(!event.getCaster().world.isRemote && !player.isCreative()
&& Wizardry.settings.discoveryMode){
// Sound and text only happen server-side, in survival, with discovery mode on, and only when
// the spell wasn't cast using commands.
WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
player.sendMessage(new TextComponentTranslation("spell.discover",
event.getSpell().getNameForTranslationFormatted()));
}
@@ -155,33 +178,59 @@ public final class WizardryEventHandler {
}
}
@SubscribeEvent(priority = EventPriority.LOW)
public static void onDiscoverSpellEvent(DiscoverSpellEvent event){
if(event.getEntityPlayer() instanceof EntityPlayerMP){
WizardryAdvancementTriggers.discover_spell.trigger((EntityPlayerMP)event.getEntityPlayer(), event.getSpell(), event.getSource());
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
if(event.getTarget() != null && event.getEntityLiving() instanceof EntityLiving
&& event.getTarget().isPotionActive(WizardryPotions.muffle)){
Vec3d vec = event.getTarget().getPositionEyes(1).subtract(event.getEntity().getPositionEyes(1));
// Find the angle between the direction the mob is looking and the direction the player is in
// Angle between a and b = acos((a.b) / (|a|*|b|))
double angle = Math.acos(vec.dotProduct(event.getEntity().getLookVec()) / vec.length());
System.out.println(angle);
// If the player is not within the 144-degree arc in front of the mob, it won't detect them
if(angle > 0.4 * Math.PI){
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
}
/* There is a subtle but important difference between LivingAttackEvent and LivingHurtEvent - LivingAttackEvent
* fires immediately when attackEntityFrom is called, whereas LivingHurtEvent only fires if the attack actually
* succeeded, i.e. if the entity in question takes damage (though the event is fired before that so you can cancel
* the damage). Things are processed in the following order: * LivingAttackEvent * - Invulnerability -
* Already-dead-ness - Fire resistance - Helmets vs. falling things - Hurt resistant time - Invulnerability (again)
* * LivingHurtEvent * - Armour - Potions - Health is finally changed Of course, there are no guarantees that other
* the damage). Things are processed in the following order:
*
* * LivingAttackEvent *
* - Invulnerability
* - Already-dead-ness
* - Fire resistance
* - Helmets vs. falling things
* - Hurt resistant time
* - Invulnerability (again)
* * LivingHurtEvent *
* - Armour
* - Potions
* * LivingDamageEvent *
* - Health is finally changed
*
* Of course, there are no guarantees that other
* mods hooking into these two events will be called before or after yours, but you can have some degree of control
* by choosing which event to use. EDIT: Actually, there are. Firstly, you can set a priority in the @SubscribeEvent
* annotation which defines how early (higher priority) or late (lower priority) the method is called. Methods with
* the same priority are sorted alphabetically by mod id (so it's safe to assume wizardry would be fairly late on!).
* I wonder if there are any conventions for what sort of things take what priority...? */
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.LOW) // Low priority in case the event gets cancelled at default priority
public static void onLivingAttackEvent(LivingAttackEvent event){
// Prevents any damage to allies from magic if friendly fire is disabled
if(!Wizardry.settings.friendlyFire && event.getSource() != null
&& event.getSource().getTrueSource() instanceof EntityPlayer && event.getEntity() instanceof EntityPlayer
&& event.getSource() instanceof IElementalDamage){
if(WizardryUtilities.isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(),
(EntityPlayer)event.getEntity())){
event.setCanceled(true);
// This needs to be here, since if the event is cancelled nothing else needs to happen.
return;
}
}
// Retaliatory effects
// These are better off here because the revenge effects are pretty similar, and I'd rather keep the (lengthy)
// if statement in one place.
@@ -192,47 +241,43 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
World world = event.getEntityLiving().world;
// Fireskin
if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
&& !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
attacker.setFire(5);
if(attacker.getDistance(event.getEntityLiving()) < 10){
// Ice Shroud
if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud)
&& !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
// Fireskin
if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
&& !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
attacker.setFire(Spells.fire_breath.getProperty(Spell.BURN_DURATION).intValue());
// Static Aura
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
// Ice Shroud
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,
Spells.ice_shroud.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.ice_shroud.getProperty(Spell.EFFECT_STRENGTH).intValue()));
if(!world.isRemote){
EntityArc arc = new EntityArc(world);
arc.setEndpointCoords(event.getEntityLiving().posX, event.getEntityLiving().posY + 1,
event.getEntityLiving().posZ, attacker.posX, attacker.posY + attacker.height / 2,
attacker.posZ);
world.spawnEntity(arc);
}else{
for(int i = 0; i < 8; i++){
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world,
attacker.posX + world.rand.nextFloat() - 0.5, attacker.getEntityBoundingBox().minY
+ attacker.height / 2 + world.rand.nextFloat() * 2 - 1,
attacker.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3);
world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, attacker.posX + world.rand.nextFloat() - 0.5,
attacker.getEntityBoundingBox().minY + attacker.height / 2 + world.rand.nextFloat() * 2
- 1,
attacker.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0);
// Static Aura
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
if(world.isRemote){
ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height / 2, 0)
.target(attacker).spawn(world);
ParticleBuilder.spawnShockParticles(world, attacker.posX,
attacker.getEntityBoundingBox().minY + attacker.height / 2, attacker.posZ);
}
}
attacker.attackEntityFrom(
MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.SHOCK, true), 4.0f);
attacker.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(),
DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType());
attacker.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
}
}
}
}
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.LOW) // Again, we don't want these effects if the event is cancelled
public static void onLivingHurtEvent(LivingHurtEvent event){
// Flaming and freezing swords
@@ -241,7 +286,7 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
// Players can only ever attack with their main hand, so this is the right method to use here.
if(!attacker.getHeldItemMainhand().isEmpty() && attacker.getHeldItemMainhand().getItem() instanceof ItemSword){
if(!attacker.getHeldItemMainhand().isEmpty() && ImbueWeapon.isSword(attacker.getHeldItemMainhand().getItem())){
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon,
attacker.getHeldItemMainhand());
@@ -282,30 +327,13 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
if(player.world.isRemote) hackilyFixContinuousSpellCasting(player);
// Mana flask crafting
if(player.openContainer instanceof ContainerWorkbench){
IInventory craftMatrix = ((ContainerWorkbench)player.openContainer).craftMatrix;
ItemStack output = ((ContainerWorkbench)player.openContainer).craftResult.getStackInSlot(0);
processManaFlaskCrafting(craftMatrix, output);
}else if(player.openContainer instanceof ContainerPlayer){
IInventory craftMatrix = ((ContainerPlayer)player.openContainer).craftMatrix;
ItemStack output = ((ContainerPlayer)player.openContainer).craftResult.getStackInSlot(0);
// Unfortunately I have no choice but to call this method every tick when the player isn't using another
// inventory, since the only thing tracking whether the player is looking at their inventory is the GUI
// itself, which is client-side only.
processManaFlaskCrafting(craftMatrix, output);
}
}
// Experimental animation feature
// if(event.getEntityLiving().isHandActive() && event.getEntityLiving().getActiveItemStack().getItemUseAction() == WizardryUtilities.POINT){
// event.getEntityLiving().isSwingInProgress = true;
// event.getEntityLiving().swingProgress = 1f;
// event.getEntityLiving().prevSwingProgress = 1;
// event.getEntityLiving().swingingHand = event.getEntityLiving().getActiveHand();
// }
if(event.getEntityLiving().world.isRemote){
@@ -316,13 +344,14 @@ public final class WizardryEventHandler {
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
if(spell != null && spell != Spells.none){
if(spell != null && spell != Spells.none){ // IntelliJ is wrong, do NOT remove the null check!
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(event.getEntityLiving(), spell, modifiers,
SpellCastEvent.Source.NPC, 0))){
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(SpellCastEvent.Source.NPC, spell, event.getEntityLiving(),
modifiers, 0))){
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
// TODO: This implementation of modifiers relies on them being accessible client-side.
// Right now that doesn't matter because NPCs don't use modifiers, but they might in future
((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
}
}
@@ -330,7 +359,7 @@ public final class WizardryEventHandler {
}
}
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.LOWEST) // No siphoning if the event is cancelled, that could be exploited...
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getTrueSource() instanceof EntityPlayer){
@@ -339,108 +368,83 @@ public final class WizardryEventHandler {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(stack.getItem() instanceof ItemWand && stack.isItemDamaged()
if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
&& WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
int damage = stack.getItemDamage()
- Constants.SIPHON_MANA_PER_LEVEL
int mana = Constants.SIPHON_MANA_PER_LEVEL
* WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade)
- player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(damage < 0) damage = 0;
stack.setItemDamage(damage);
break;
+ player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(ItemArtefact.isArtefactActive(player, WizardryItems.ring_siphoning)) mana *= 1.3f;
((IManaStoringItem)stack.getItem()).rechargeMana(stack, mana);
break; // Only recharge one item per kill
}
}
}
}
// These two are lifted from EntityLivingBase#travel
private static final double LIVING_ENTITY_GRAVITY = 0.08;
private static final double LIVING_ENTITY_DRAG = 0.98;
private static final double LIVING_ENTITY_TERMINAL_VELOCITY = 3.92; // From Minecraft Wiki!
private static final double LOG_LIVING_ENTITY_DRAG = Math.log(LIVING_ENTITY_DRAG);
private static final double FALL_TICKS_ERROR_CORRECTION = 0.500841776608447;
@SubscribeEvent // Priority doesn't matter here, we're only setting event fields so if it's cancelled it won't matter
public static void onLivingFallEvent(LivingFallEvent event){
// Why is fall damage based on distance fallen? Why? Who on earth came up with that? It makes no sense whatsoever!
if(Wizardry.settings.replaceVanillaFallDamage && !Loader.isModLoaded("speedbasedfalldamage")){
// We want to keep the fall damage EXACTLY THE SAME for free, uninterrupted falls, but fix the weirdness
// caused when something else changes the entity's velocity
// All living entities have a gravity of 0.08b/t^2
// Therefore it would be simple to say v^2 = u^2 + 2gs gives the equivalent fall distance as motionY^2 / 0.16
// However, Minecraft also has a drag of 0.02 * the velocity, so we actually expect a slightly different value
// Much maths later...
double v = event.getEntity().motionY;
// Players are weird, their velocity somehow resets on the server just before this event fires so
// instead we're storing the y velocity from the previous tick in WizardData and retrieving it here
// Of course, if another mod screws things up and sets a player's velocity client-side only then this won't
// work ...but it's better than having clients calculate their own fall damage
if(event.getEntity() instanceof EntityPlayer){
WizardData data = WizardData.get((EntityPlayer)event.getEntity());
if(data != null){
v = data.prevMotionY;
}
}
if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){
WizardryAdvancementTriggers.self_destruct.triggerFor(player);
}
}
}
// At terminal velocity, there's no way of finding fall distance from velocity, and the entity is probably dead anyway!
if(v > -3.9){
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
// TODO: Really, this should be in a loot table (mob_additions), however I can't seem to find a way of
// automatically adding it to all subclasses of IMob.
// Evil wizards drop spell books themselves
if(event.getEntityLiving() instanceof IMob && !(event.getEntityLiving() instanceof EntityEvilWizard)
// TODO: Backport when you backport the new summoned creature system.
&& !(event.getEntityLiving() instanceof ISummonedCreature)
&& event.getSource().getTrueSource() instanceof EntityPlayer && Wizardry.settings.spellBookDropChance > 0){
// Just to make the code more readable, java will replace them all with numbers anyway
double g = LIVING_ENTITY_GRAVITY;
double f = LIVING_ENTITY_DRAG;
double lnf = LOG_LIVING_ENTITY_DRAG;
double tv = LIVING_ENTITY_TERMINAL_VELOCITY;
// This does exactly what the entity drop method does, but with a different random number so that the
// spell book doesn't always drop with other rare drops.
int rareDropNumber = event.getEntity().world.rand.nextInt(200) - event.getLootingLevel();
if(rareDropNumber < Wizardry.settings.spellBookDropChance){
// Drops a spell book
int id = WizardryUtilities.getStandardWeightedRandomSpellId(event.getEntity().world.rand);
// Work backwards from y velocity to get fall time
// logs are probably slow but it's not like this gets calculated every tick, and we only need one log
double t = Math.log(((-v - tv) * lnf) / g) / lnf; // Log the number over log the base
event.getDrops()
.add(new EntityItem(event.getEntityLiving().world, event.getEntityLiving().posX,
event.getEntityLiving().posY, event.getEntityLiving().posZ,
new ItemStack(WizardryItems.spell_book, 1, id)));
}
}
}
// Because time is in discrete ticks, the above equation for t results in a constant error of
// +0.500841776608447, so I guess we can just subtract it... if it works it works I guess!
t -= FALL_TICKS_ERROR_CORRECTION; // Don't cast to int or perform any rounding
// Private helper methods
// ================================================================================================================
// Now work forwards from t to find the effective fall distance, i.e. the distance the entity would
// have to freefall to reach the same velocity
// Don't ask me where the 196 is from, again, it just works!
double y = (g * Math.pow(f, t)) / (lnf*lnf) + tv * (t) - 196;
/**
* Detects inconsistencies between player.getActiveItemStack and the actual itemstack and forces them to be equal.
* Fixes issue #25.
*
* @param player
*/
private static void hackilyFixContinuousSpellCasting(EntityPlayer player){
if(player.isHandActive() && player.getHeldItem(player.getActiveHand()).getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(player.getHeldItem(player.getActiveHand())).isContinuous){
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())){
player.setHeldItem(player.getActiveHand(), player.getActiveItemStack());
}
}
}
// DEBUG
// if(event.getEntity() instanceof EntityPlayer){
// Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y);
// }
private static void processManaFlaskCrafting(IInventory craftMatrix, ItemStack output){
// Charges wand using mana flask. It is here rather than in the crafting handler so the result displays
// the proper damage before it is actually crafted.
boolean flag = false;
ItemStack wand = ItemStack.EMPTY;
ItemStack armour = ItemStack.EMPTY;
for(int i = 0; i < craftMatrix.getSizeInventory(); i++){
ItemStack itemstack = craftMatrix.getStackInSlot(i);
if(itemstack.getItem() == WizardryItems.mana_flask){
flag = true;
}
if(itemstack.getItem() instanceof ItemWand){
wand = itemstack;
}
if(itemstack.getItem() instanceof ItemWizardArmour){
armour = itemstack;
}
}
if(output.getItem() instanceof ItemWand && flag && !wand.isEmpty()){
output.setTagCompound((wand.getTagCompound()));
if(wand.getItemDamage() - Constants.MANA_PER_FLASK < 0){
output.setItemDamage(0);
}else{
output.setItemDamage(wand.getItemDamage() - Constants.MANA_PER_FLASK);
}
}
if(output.getItem() instanceof ItemWizardArmour && flag && !armour.isEmpty()){
output.setTagCompound((armour.getTagCompound()));
if(armour.getItemDamage() - Constants.MANA_PER_FLASK < 0){
output.setItemDamage(0);
}else{
output.setItemDamage(wand.getItemDamage() - Constants.MANA_PER_FLASK);
event.setDistance((float)y);
}
}
}
@@ -1,12 +1,12 @@
package electroblob.wizardry;
import java.util.Set;
import electroblob.wizardry.client.GuiConfigWizardry;
import electroblob.wizardry.client.gui.config.GuiConfigWizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.IModGuiFactory;
import java.util.Set;
public class WizardryGuiFactory implements IModGuiFactory {
@Override
@@ -40,20 +40,20 @@ public class WizardryGuiHandler implements IGuiHandler {
if(id == ARCANE_WORKBENCH){
TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
if(tileEntity instanceof TileEntityArcaneWorkbench){
return new electroblob.wizardry.client.GuiArcaneWorkbench(player.inventory,
return new electroblob.wizardry.client.gui.GuiArcaneWorkbench(player.inventory,
(TileEntityArcaneWorkbench)tileEntity);
}
}else if(id == WIZARD_HANDBOOK && (player.getHeldItemMainhand().getItem() instanceof ItemWizardHandbook
|| player.getHeldItemOffhand().getItem() instanceof ItemWizardHandbook)){
return new electroblob.wizardry.client.GuiWizardHandbook();
return new electroblob.wizardry.client.gui.handbook.GuiWizardHandbook();
}else if(id == SPELL_BOOK){
if(player.getHeldItemMainhand().getItem() instanceof ItemSpellBook){
return new electroblob.wizardry.client.GuiSpellBook(Spell.get(player.getHeldItemMainhand().getItemDamage()));
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemMainhand().getItemDamage()));
}else if(player.getHeldItemOffhand().getItem() instanceof ItemSpellBook){
return new electroblob.wizardry.client.GuiSpellBook(Spell.get(player.getHeldItemOffhand().getItemDamage()));
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemOffhand().getItemDamage()));
}
}else if(id == PORTABLE_CRAFTING){
return new electroblob.wizardry.client.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z));
return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z));
}
return null;
}
@@ -1,68 +0,0 @@
package electroblob.wizardry;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import net.minecraft.client.Minecraft;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
public class WizardryKeyHandler {
boolean NkeyPressed = false;
boolean BkeyPressed = false;
boolean NkeyAlreadyPressed = false;
boolean BkeyAlreadyPressed = false;
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event){
// Key pressed
if(Keyboard.getEventKeyState()){
if(Wizardry.proxy instanceof ClientProxy){
if(ClientProxy.NEXT_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(!NkeyPressed){
NkeyPressed = true;
}else{
NkeyAlreadyPressed = true;
}
if(!NkeyAlreadyPressed){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}
}
if(ClientProxy.PREVIOUS_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(!BkeyPressed){
BkeyPressed = true;
}else{
BkeyAlreadyPressed = true;
}
if(!BkeyAlreadyPressed){
// Packet building
IMessage msg = new PacketControlInput.Message(
PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}
}
}
}
// Key released
else{
if(NkeyPressed){
NkeyPressed = false;
NkeyAlreadyPressed = false;
}else if(BkeyPressed){
BkeyPressed = false;
BkeyAlreadyPressed = false;
}
}
}
}
@@ -1,948 +0,0 @@
package electroblob.wizardry;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.BlockChest;
import net.minecraft.block.BlockColored;
import net.minecraft.block.BlockLeaves;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.BlockPlanks;
import net.minecraft.block.BlockSlab;
import net.minecraft.block.BlockSlab.EnumBlockHalf;
import net.minecraft.block.BlockTorch;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Biomes;
import net.minecraft.init.Blocks;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.EnumDyeColor;
import net.minecraft.item.ItemDoor;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraft.world.gen.feature.WorldGenMinable;
import net.minecraft.world.storage.loot.LootContext;
import net.minecraft.world.storage.loot.LootTable;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.common.IPlantable;
import net.minecraftforge.fml.common.IWorldGenerator;
public class WizardryWorldGenerator implements IWorldGenerator {
/** The string identifier for wizard tower chests, used in ChestGenHooks. */
public static final String WIZARD_TOWER = Wizardry.MODID + "wizardTower";
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator,
IChunkProvider chunkProvider){
for(int id : Wizardry.settings.oreDimensions){
if(id == world.provider.getDimension()) this.addOreSpawn(WizardryBlocks.crystal_ore.getDefaultState(),
world, random, chunkX * 16, chunkZ * 16, 16, 16, 5, 7, 5, 30);
}
for(int id : Wizardry.settings.flowerDimensions){
if(id == world.provider.getDimension()) this.generatePlant(WizardryBlocks.crystal_flower.getDefaultState(),
world, random, chunkX * 16, chunkZ * 16, 2, 20);
}
if(world.getWorldInfo().isMapFeaturesEnabled()){
for(int id : Wizardry.settings.towerDimensions){
if(id == world.provider.getDimension())
this.generateWizardTower(world, random, chunkX * 16, chunkZ * 16);
}
}
}
/**
* Adds an Ore Spawn to Minecraft. Simply register all Ores to spawn with this method in your Generation method in
* your IWorldGeneration extending Class
*
* @param The Block to spawn
* @param The World to spawn in
* @param A Random object for retrieving random positions within the world to spawn the Block
* @param An int for passing the X-Coordinate for the Generation method
* @param An int for passing the Z-Coordinate for the Generation method
* @param An int for setting the maximum X-Coordinate values for spawning on the X-Axis on a Per-Chunk basis
* @param An int for setting the maximum Z-Coordinate values for spawning on the Z-Axis on a Per-Chunk basis
* @param An int for setting the maximum size of a vein
* @param An int for the Number of chances available for the Block to spawn per-chunk
* @param An int for the minimum Y-Coordinate height at which this block may spawn
* @param An int for the maximum Y-Coordinate height at which this block may spawn
**/
public void addOreSpawn(IBlockState state, World world, Random random, int blockXPos, int blockZPos, int maxX,
int maxZ, int maxVeinSize, int chancesToSpawn, int minY, int maxY){
// int maxPossY = minY + (maxY - 1);
assert maxY > minY : "The maximum Y must be greater than the Minimum Y";
assert maxX > 0 && maxX <= 16 : "addOreSpawn: The Maximum X must be greater than 0 and less than 16";
assert minY > 0 : "addOreSpawn: The Minimum Y must be greater than 0";
assert maxY < 256 && maxY > 0 : "addOreSpawn: The Maximum Y must be less than 256 but greater than 0";
assert maxZ > 0 && maxZ <= 16 : "addOreSpawn: The Maximum Z must be greater than 0 and less than 16";
int diffBtwnMinMaxY = maxY - minY;
for(int x = 0; x < chancesToSpawn; x++){
int posX = blockXPos + random.nextInt(maxX);
int posY = minY + random.nextInt(diffBtwnMinMaxY);
int posZ = blockZPos + random.nextInt(maxZ);
(new WorldGenMinable(state, maxVeinSize)).generate(world, random, new BlockPos(posX, posY, posZ));
}
}
/**
* Generates the specified plant randomly throughout the world.
*
* @param block The plant block
* @param world The world
* @param random A random instance
* @param x The x coord of the first block in the chunk
* @param z The y coord of the first block in the chunk
* @param chancesToSpawn Number of chances to spawn a flower patch
* @param groupSize The number of times to try generating a flower per flower patch spawn
*/
public void generatePlant(IBlockState state, World world, Random random, int x, int z, int chancesToSpawn,
int groupSize){
for(int i = 0; i < chancesToSpawn; i++){
int randPosX = x + random.nextInt(16);
int randPosY = random.nextInt(256);
int randPosZ = z + random.nextInt(16);
for(int l = 0; l < groupSize; ++l){
int i1 = randPosX + random.nextInt(8) - random.nextInt(8);
int j1 = randPosY + random.nextInt(4) - random.nextInt(4);
int k1 = randPosZ + random.nextInt(8) - random.nextInt(8);
BlockPos pos = new BlockPos(i1, j1, k1);
if(world.isBlockLoaded(pos) && world.isAirBlock(pos) && (!world.provider.isNether() || j1 < 127)
&& state.getBlock().canPlaceBlockOnSide(world, pos, EnumFacing.UP)){
world.setBlockState(pos, state, 2);
}
}
}
}
/**
* Generates wizard towers randomly throughout the world.
*/
public void generateWizardTower(World world, Random random, int chunkX, int chunkZ){
// Allows the config file to set the rarity value to 0 to disable tower generation completely.
if(Wizardry.settings.towerRarity == 0) return;
// Compensates for the lack of space in forests. Math.max is required since treeless biomes have treesPerChunk =
// -999
// double treeFactor = 70 - Math.max((double)world.getBiomeGenForCoords(chunkX,
// chunkZ).theBiomeDecorator.treesPerChunk, 0) * 1.5d;
// Multiplied by 70 to (roughly) retain the old rarity scale
if(random.nextInt((int)(Wizardry.settings.towerRarity * 70)) == 0){
BlockPos origin = new BlockPos(chunkX + random.nextInt(16), 0, chunkZ + random.nextInt(16));
// Despite what its name suggests, this method does not return the position of a liquid. It is in fact
// exactly what is needed here since it is used for placing villages and stuff, and doesn't include leaves
// or other foliage.
origin = origin.up(world.getTopSolidOrLiquidBlock(origin).getY() - 1);
int[][][] towerBlueprint = towerBlueprintSmall;
switch(random.nextInt(4)){
case 0:
towerBlueprint = towerBlueprintSmall;
break;
case 1:
towerBlueprint = towerBlueprintMedium;
break;
case 2:
towerBlueprint = towerBlueprintTall;
break;
case 3:
towerBlueprint = towerBlueprintDouble;
break;
}
// 0 = West, 1 = North, 2 = East, 3 = South (The way you would face when walking out of the door)
EnumFacing orientation = EnumFacing.getHorizontal(random.nextInt(4));
boolean flip = random.nextBoolean();
if(checkSpaceForTower(world, origin, towerBlueprint, orientation, flip)){
// == Setup ==
boolean evilWizard = random.nextInt(5) == 0;
IBlockState wallMaterial = Blocks.COBBLESTONE.getDefaultState();
BlockPlanks.EnumType woodType = BlockPlanks.EnumType.OAK;
Biome biome = world.getBiome(origin);
// The order of these is somewhat important in that biomes can be many types, so the last of these
// checks
// that is true gets priority. Generally speaking, the later the check, the more specific it is.
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DENSE))
wallMaterial = Blocks.MOSSY_COBBLESTONE.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SWAMP))
wallMaterial = Blocks.MOSSY_COBBLESTONE.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SANDY))
wallMaterial = Blocks.SANDSTONE.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.NETHER))
wallMaterial = Blocks.NETHER_BRICK.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.MOUNTAIN))
wallMaterial = Blocks.STONEBRICK.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.MESA))
wallMaterial = Blocks.HARDENED_CLAY.getDefaultState();
// Unfortunately, I can't check all the wood types with the biome dictionary
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS))
woodType = BlockPlanks.EnumType.SPRUCE;
if(biome == Biomes.BIRCH_FOREST || biome == Biomes.BIRCH_FOREST_HILLS)
woodType = BlockPlanks.EnumType.BIRCH;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) woodType = BlockPlanks.EnumType.JUNGLE;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SAVANNA)) woodType = BlockPlanks.EnumType.ACACIA;
// Not technically a tree type, but I think it fits quite well anyway
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SPOOKY))
woodType = BlockPlanks.EnumType.DARK_OAK;
IBlockState[] blockStateList = new IBlockState[]{null, Blocks.AIR.getDefaultState(),
Blocks.PLANKS.getDefaultState().withProperty(BlockPlanks.VARIANT, woodType),
Blocks.BOOKSHELF.getDefaultState(),
Blocks.STAINED_HARDENED_CLAY.getDefaultState().withProperty(BlockColored.COLOR,
EnumDyeColor.values()[random.nextInt(EnumDyeColor.values().length)]),
Blocks.WOODEN_SLAB.getDefaultState().withProperty(BlockSlab.HALF, EnumBlockHalf.BOTTOM),
Blocks.WOODEN_SLAB.getDefaultState().withProperty(BlockSlab.HALF, EnumBlockHalf.TOP),
wallMaterial, Blocks.GLASS_PANE.getDefaultState(), Blocks.OAK_DOOR.getDefaultState(),
WizardryBlocks.arcane_workbench.getDefaultState(), Blocks.TORCH.getDefaultState(),
evilWizard ? Blocks.CHEST.getDefaultState() : Blocks.BOOKSHELF.getDefaultState()};
Set<BlockPos> blocksPlaced = new HashSet<BlockPos>();
// == Foundations ==
// Fills in foundations. This is done first so the door always has something to be placed on.
boolean flag = true;
// BlockPos is immutable, so I'm not sure if simply saying pos1 = pos will be sufficient.
BlockPos layerCentre = new BlockPos(origin);
while(flag){
flag = false;
for(BlockPos offset : foundationLayer){
if(!world.isBlockNormalCube(layerCentre.add(offset), false)){
world.setBlockState(layerCentre.add(offset), wallMaterial);
blocksPlaced.add(layerCentre.add(offset));
// Keeps going as long as something was filled in.
flag = true;
}
}
layerCentre = layerCentre.down();
}
// == Main Structure ==
// It is assumed that the width of the blueprint is the same all the way up, and that the layers are
// square.
int width = towerBlueprint[0].length - 1;
// x, y and z are the position the block is being put in.
// x1, y and z1 are the position in the blueprint which determines which block is being placed.
int x1 = 0, z1 = 0;
for(int y = 0; y < towerBlueprint.length; y++){
for(int z = 0; z < towerBlueprint[y].length; z++){
for(int x = 0; x < towerBlueprint[y][z].length; x++){
BlockPos pos = origin.add(x - width / 2, y, z - width / 2);
switch(orientation){
case WEST:
x1 = flip ? width - x : x;
z1 = z;
break;
case NORTH:
x1 = z;
z1 = flip ? x : width - x;
break;
case EAST:
x1 = flip ? x : width - x;
z1 = width - z;
break;
case SOUTH:
x1 = width - z;
z1 = flip ? width - x : x;
break;
default:
break;
}
if(blockStateList[towerBlueprint[y][z1][x1]] != null
&& blockStateList[towerBlueprint[y][z1][x1]].getBlock() != Blocks.TORCH
&& blockStateList[towerBlueprint[y][z1][x1]].getBlock() != Blocks.CHEST){
if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.OAK_DOOR){
// Rotates the door depending on whether flip is true.
ItemDoor.placeDoor(
world, pos, flip
? EnumFacing.getHorizontal(3 - orientation.getHorizontalIndex())
.getOpposite()
: orientation.rotateYCCW(),
Blocks.OAK_DOOR, false);
}else{
world.setBlockState(pos, blockStateList[towerBlueprint[y][z1][x1]], 2);
}
blocksPlaced.add(pos);
}
}
}
}
// == Extras ==
// Torches are done afterwards so they don't fall off
// Chests are also done afterwards, because for some reason the chest decides which way round to face
// itself, after it has been placed, based on the surrounding blocks... but if it was placed during
// the rest of the tower generation, some of those blocks wouldn't exist, hence it must be done here.
for(int y = 0; y < towerBlueprint.length; y++){
for(int z = 0; z < towerBlueprint[y].length; z++){
for(int x = 0; x < towerBlueprint[y][z].length; x++){
BlockPos pos = origin.add(x - width / 2, y, z - width / 2);
switch(orientation){
case WEST:
x1 = flip ? width - x : x;
z1 = z;
break;
case NORTH:
x1 = z;
z1 = flip ? x : width - x;
break;
case EAST:
x1 = flip ? x : width - x;
z1 = width - z;
break;
case SOUTH:
x1 = width - z;
z1 = flip ? width - x : x;
break;
default:
break;
}
if(blockStateList[towerBlueprint[y][z1][x1]] != null){
if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.TORCH){
if(placeTorch(world, pos, true)){
blocksPlaced.add(pos);
}else{
Wizardry.logger.info("Attempted to generate a torch at " + pos + " in " + world
+ ", but failed!");
}
}
// World should always be a WorldServer, but it's worth checking anyway.
if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.CHEST
&& world instanceof WorldServer){
if(placeChest(world, pos)){
blocksPlaced.add(pos);
LootTable table = world.getLootTableManager().getLootTableFromLocation(
new ResourceLocation(Wizardry.MODID, "chests/wizard_tower"));
IInventory inventory = (IInventory)world.getTileEntity(pos);
LootContext context = new LootContext.Builder((WorldServer)world).build();
table.fillInventory(inventory, random, context);
}else{
Wizardry.logger.info("Attempted to generate a chest at " + pos + " in " + world
+ ", but failed!");
}
}
}
}
}
}
if(evilWizard){
EntityEvilWizard wizard = new EntityEvilWizard(world);
wizard.hasTower = true;
wizard.setLocationAndAngles(origin.getX() + 1.5, origin.getY() + towerBlueprint.length - 9.5,
origin.getZ() + 1.5, 0, 0);
wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
world.spawnEntity(wizard);
}else{
EntityWizard wizard = new EntityWizard(world);
wizard.setLocationAndAngles(origin.getX() + 1.5, origin.getY() + towerBlueprint.length - 9.5,
origin.getZ() + 1.5, 0, 0);
wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
wizard.setTowerBlocks(blocksPlaced);
world.spawnEntity(wizard);
}
}
}
}
/**
* Places a torch at the given position in the given world and automatically assigns an appropriate state. Order of
* priority is U-S-W-N-E, or S-W-N-E-U if wallPriority is true.
*
* @param world The world to place the torch in.
* @param pos The position to place the torch at.
* @param wallPriority True to prioritise wall torches, false to prioritise floor torches.
* @return True if the torch was placed, false if it is not possible.
*/
private static boolean placeTorch(World world, BlockPos pos, boolean wallPriority){
for(EnumFacing facing : ArrayUtils.add(EnumFacing.HORIZONTALS, wallPriority ? 4 : 0, EnumFacing.UP)){
if(world.isSideSolid(pos.offset(facing.getOpposite()), facing)){
world.setBlockState(pos, Blocks.TORCH.getDefaultState().withProperty(BlockTorch.FACING, facing));
return true;
}
}
return false;
}
/**
* Places a chest at the given position in the given world and automatically assigns an appropriate state. Order of
* priority is S-W-N-E.
*
* @param world The world to chest the torch in.
* @param pos The position to chest the torch at.
* @return True if the chest was placed, false if it is not possible.
*/
private static boolean placeChest(World world, BlockPos pos){
for(EnumFacing facing : EnumFacing.HORIZONTALS){
if(world.isAirBlock(pos.offset(facing))){
world.setBlockState(pos, Blocks.CHEST.getDefaultState().withProperty(BlockChest.FACING, facing));
return true;
}
}
return false;
}
/**
* Checks whether a tower generated at the given coordinates will intersect any solid or liquid blocks. Only tests
* for liquids (not solid blocks) for the first four layers to account for the floor and for sloping terrain.
*
* @return True if none of the blocks which the tower would replace are solid or liquid. Dirt, stone etc., water and
* lava count, as do logs, but leaves and plants don't.
*/
private static boolean checkSpaceForTower(World world, BlockPos pos, int[][][] towerBlueprint,
EnumFacing orientation, boolean flip){
// x, y and z are the position the block is being put in.
// x1, y and z1 are the position in the blueprint which determines which block is being placed.
int x1 = 0, z1 = 0;
// It is assumed that the width of the blueprint is the same all the way up, and that the layers are square.
int width = towerBlueprint[0].length - 1;
for(int y = 0; y < towerBlueprint.length; y++){
for(int z = 0; z < towerBlueprint[y].length; z++){
for(int x = 0; x < towerBlueprint[y][z].length; x++){
BlockPos pos1 = pos.add(x - width / 2, y, z - width / 2);
switch(orientation){
case WEST:
x1 = flip ? width - x : x;
z1 = z;
break;
case NORTH:
x1 = z;
z1 = flip ? x : width - x;
break;
case EAST:
x1 = flip ? x : width - x;
z1 = width - z;
break;
case SOUTH:
x1 = width - z;
z1 = flip ? width - x : x;
break;
default:
break;
}
if(towerBlueprint[y][z1][x1] != 0 && !WizardryUtilities.canBlockBeReplacedB(world, pos1)
// TODO: Is this a better replacement for the subsequent two lines?
// && !world.getBlockState(pos1).getBlock().isFoliage(world, pos1)
&& !(world.getBlockState(pos1).getBlock() instanceof IPlantable)
&& !(world.getBlockState(pos1).getBlock() instanceof BlockLeaves)
&& (y > 3 || world.getBlockState(pos1).getBlock() instanceof BlockLiquid)){
return false;
}
}
}
}
return true;
}
/** Array of relative positions of each block in a layer of foundations. */
private static final BlockPos[] foundationLayer = new BlockPos[]{new BlockPos(-2, 0, -1), new BlockPos(-2, 0, 0),
new BlockPos(-2, 0, 1), new BlockPos(2, 0, -1), new BlockPos(2, 0, 0), new BlockPos(2, 0, 1),
new BlockPos(-1, 0, -2), new BlockPos(0, 0, -2), new BlockPos(1, 0, -2), new BlockPos(-1, 0, 2),
new BlockPos(0, 0, 2), new BlockPos(1, 0, 2),};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintSmall = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0},
{0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0},
{0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0},
{4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4},
{0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintMedium = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0},
{0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0},
{0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0},
{4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4},
{0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintTall = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0},
{0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0},
{0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0},
{4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4},
{0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintDouble = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 9, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 6, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 0, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 11, 1, 11, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 6, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 6, 7, 7, 7, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 5, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 7, 7, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 1, 1, 1, 1, 7, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 7, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 7, 8, 7, 0},
{0, 0, 0, 0, 7, 6, 1, 1, 1, 1, 1, 8, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 8, 7, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 4, 4, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 7, 7, 4},
{0, 0, 0, 0, 7, 1, 1, 11, 7, 1, 1, 7, 4}, {0, 0, 0, 0, 7, 5, 6, 1, 7, 7, 7, 7, 4},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 4, 4, 4, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 4, 4, 4, 0},
{0, 0, 0, 0, 7, 1, 1, 6, 7, 4, 1, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 5, 7, 4, 4, 4, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 0, 4, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 1, 7, 4, 1, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 4, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 4, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 2, 2, 2, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 2, 7, 0, 0, 0},
{0, 0, 0, 7, 5, 2, 2, 6, 2, 7, 4, 0, 0}, {0, 0, 0, 7, 2, 2, 2, 2, 2, 7, 0, 0, 0},
{0, 0, 0, 0, 7, 2, 2, 2, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 3, 3, 3, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 10, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 8, 1, 1, 1, 1, 3, 7, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 7, 7, 7, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 7, 1, 1, 1, 7, 4, 0, 0, 0}, {0, 0, 4, 7, 1, 1, 1, 1, 3, 7, 4, 0, 0},
{0, 0, 4, 7, 1, 1, 1, 1, 3, 7, 4, 0, 0}, {0, 0, 4, 7, 1, 1, 1, 1, 12, 7, 4, 0, 0},
{0, 0, 0, 4, 7, 1, 1, 1, 7, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 7, 7, 7, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0},
{0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}};
}
@@ -1,61 +0,0 @@
package electroblob.wizardry.advancement;
import electroblob.wizardry.Wizardry;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementManager;
import net.minecraft.advancements.AdvancementProgress;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.ResourceLocation;
/**
* This is a provisory class for the transition to 1.12
* It contains an enum with all the advancements of this mod
* and an helper to grant advancements on the server side only (like the command)
* @author Corail31
* @since Wizardry 4.1
*/
public class AdvancementHelper {
public enum EnumAdvancement {
crystal,
arcane_initiate,
apprentice,
master,
all_spells,
wizard_trade,
buy_master_spell,
freeze_blaze,
charge_creeper,
frankenstein,
special_upgrade,
craft_flask,
elemental,
armour_set,
legendary,
self_destruct,
pig_tornado,
jam_wizard,
slime_skeleton,
anger_wizard,
defeat_evil_wizard,
max_out_wand,
element_master,
identify_spell;
}
public static boolean grantAdvancement(EntityPlayer player, EnumAdvancement advancementName) {
if (player == null) { return false; }
if (player.world.isRemote) { return true; }
EntityPlayerMP player_mp = player.getServer().getPlayerList().getPlayerByUUID(player.getUniqueID());
AdvancementManager am = player_mp.getServerWorld().getAdvancementManager();
Advancement advancement = am.getAdvancement(new ResourceLocation(Wizardry.MODID, advancementName.name()));
if (advancement == null) { return false; }
AdvancementProgress advancementprogress = player_mp.getAdvancements().getProgress(advancement);
if (!advancementprogress.isDone()) {
for (String criteria : advancementprogress.getRemaningCriteria()) {
player_mp.getAdvancements().grantCriterion(advancement, criteria);
}
}
return true;
}
}
@@ -0,0 +1,135 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Advancement trigger for things done in the arcane workbench. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class ArcaneWorkbenchTrigger implements ICriterionTrigger<ArcaneWorkbenchTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, ArcaneWorkbenchTrigger.Listeners> listeners = Maps.newHashMap();
public ArcaneWorkbenchTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<ArcaneWorkbenchTrigger.Instance> listener){
ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new ArcaneWorkbenchTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<ArcaneWorkbenchTrigger.Instance> listener){
ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public ArcaneWorkbenchTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
return new ArcaneWorkbenchTrigger.Instance(this.id, ItemPredicate.deserialize(json.get("item")));
}
public void trigger(EntityPlayerMP player, ItemStack stack){
ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(stack);
}
}
public static class Instance extends AbstractCriterionInstance {
private final ItemPredicate item;
public Instance(ResourceLocation criterionIn, ItemPredicate item){
super(criterionIn);
this.item = item;
}
public boolean test(ItemStack stack){
return this.item.test(stack);
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<ArcaneWorkbenchTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<ArcaneWorkbenchTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<ArcaneWorkbenchTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(ItemStack stack){
List<Listener<ArcaneWorkbenchTrigger.Instance>> list = null;
for(Listener<ArcaneWorkbenchTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(stack)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<ArcaneWorkbenchTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -1,10 +1,9 @@
package electroblob.wizardry.util;
package electroblob.wizardry.advancement;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.Wizardry;
import net.minecraft.advancements.ICriterionInstance;
import net.minecraft.advancements.ICriterionTrigger;
@@ -34,7 +33,7 @@ public class CustomAdvancementTrigger implements ICriterionTrigger<CustomAdvance
* advancement to happen.
*/
public static class Instance extends AbstractCriterionInstance {
public Instance(ResourceLocation triggerId) {
public Instance(ResourceLocation triggerId) {
super(triggerId);
}
}
@@ -0,0 +1,139 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.spell.Spell;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Advancement trigger which is triggered when a spell is cast. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class SpellCastTrigger implements ICriterionTrigger<SpellCastTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, SpellCastTrigger.Listeners> listeners = Maps.newHashMap();
public SpellCastTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<SpellCastTrigger.Instance> listener){
SpellCastTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new SpellCastTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<SpellCastTrigger.Instance> listener){
SpellCastTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public SpellCastTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
return new SpellCastTrigger.Instance(this.id, SpellPredicate.deserialize(json.get("spell")),
ItemPredicate.deserialize(json.get("item")));
}
public void trigger(EntityPlayerMP player, Spell spell, ItemStack stack){
SpellCastTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(spell, stack);
}
}
public static class Instance extends AbstractCriterionInstance {
private final SpellPredicate spell;
private final ItemPredicate item;
public Instance(ResourceLocation criterion, SpellPredicate spell, ItemPredicate item){
super(criterion);
this.spell = spell;
this.item = item;
}
public boolean test(Spell spell, ItemStack stack){
return this.spell.test(spell) && item.test(stack);
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<SpellCastTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<SpellCastTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<SpellCastTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(Spell spell, ItemStack stack){
List<Listener<SpellCastTrigger.Instance>> list = null;
for(Listener<SpellCastTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(spell, stack)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<SpellCastTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -0,0 +1,143 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.spell.Spell;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Advancement trigger that is triggered when a spell is discovered. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class SpellDiscoveryTrigger implements ICriterionTrigger<SpellDiscoveryTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, SpellDiscoveryTrigger.Listeners> listeners = Maps.newHashMap();
public SpellDiscoveryTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<SpellDiscoveryTrigger.Instance> listener){
SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new SpellDiscoveryTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<SpellDiscoveryTrigger.Instance> listener){
SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public SpellDiscoveryTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
String s = JsonUtils.getString(json, "source");
DiscoverSpellEvent.Source source = DiscoverSpellEvent.Source.byName(s);
if(source == null) throw new JsonSyntaxException("No such spell discovery source: " + s);
return new SpellDiscoveryTrigger.Instance(this.id, SpellPredicate.deserialize(json.get("spell")), source);
}
public void trigger(EntityPlayerMP player, Spell spell, DiscoverSpellEvent.Source source){
SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(spell, source);
}
}
public static class Instance extends AbstractCriterionInstance {
private final SpellPredicate spell;
private final DiscoverSpellEvent.Source source;
public Instance(ResourceLocation criterion, SpellPredicate spell, DiscoverSpellEvent.Source source){
super(criterion);
this.spell = spell;
this.source = source;
}
public boolean test(Spell spell, DiscoverSpellEvent.Source source){
return this.spell.test(spell) && source == this.source;
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<SpellDiscoveryTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<SpellDiscoveryTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<SpellDiscoveryTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(Spell spell, DiscoverSpellEvent.Source source){
List<Listener<SpellDiscoveryTrigger.Instance>> list = null;
for(Listener<SpellDiscoveryTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(spell, source)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<SpellDiscoveryTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -0,0 +1,117 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Streams;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.spell.Spell;
import net.minecraft.util.JsonUtils;
import javax.annotation.Nullable;
import java.util.Arrays;
/** Predicate used by advancement triggers to match spells. */
public class SpellPredicate {
public static final SpellPredicate ANY = new SpellPredicate();
private final Spell spell;
private final Tier[] tiers;
private final Element[] elements;
public SpellPredicate(){
this.spell = null;
this.tiers = Tier.values();
this.elements = Element.values();
}
public SpellPredicate(@Nullable Spell spell, Tier[] tiers, Element[] elements){
this.spell = spell;
this.tiers = tiers;
this.elements = elements;
}
public boolean test(Spell spell){
if(this.spell != null && spell != this.spell){
return false;
}else if(!Arrays.asList(this.tiers).contains(spell.getTier())){
return false;
}else if(!Arrays.asList(this.elements).contains(spell.getElement())){
return false;
}
return true;
}
public static SpellPredicate deserialize(@Nullable JsonElement element){
if(element != null && !element.isJsonNull()){
JsonObject jsonobject = JsonUtils.getJsonObject(element, "spell");
Spell spell = null;
if(jsonobject.has("spell")){
String s = JsonUtils.getString(jsonobject, "spell");
spell = Spell.get(s);
if(spell == null){
throw new JsonSyntaxException("Unknown spell id '" + s + "'");
}
}
Tier[] tiers = Tier.values();
if(jsonobject.has("tiers")){
try{
JsonArray array = JsonUtils.getJsonArray(jsonobject, "tiers");
tiers = Streams.stream(array)
.map(je -> Tier.fromName(JsonUtils.getString(je, "element of array tiers")))
.toArray(Tier[]::new);
}catch(IllegalArgumentException e){
throw new JsonSyntaxException("Incorrect spell predicate value", e);
}
}
Element[] elements = Element.values();
if(jsonobject.has("elements")){
try{
JsonArray array = JsonUtils.getJsonArray(jsonobject, "elements");
elements = Streams.stream(array)
.map(je -> Element.fromName(JsonUtils.getString(je, "element of array elements")))
.toArray(Element[]::new);
}catch(IllegalArgumentException e){
throw new JsonSyntaxException("Incorrect spell predicate value", e);
}
}
return new SpellPredicate(spell, tiers, elements);
}else{
return ANY;
}
}
public static SpellPredicate[] deserializeArray(@Nullable JsonElement element){
if(element != null && !element.isJsonNull()){
JsonArray jsonarray = JsonUtils.getJsonArray(element, "spells");
SpellPredicate[] predicates = new SpellPredicate[jsonarray.size()];
for(int i = 0; i < predicates.length; ++i){
predicates[i] = deserialize(jsonarray.get(i));
}
return predicates;
}else{
return new SpellPredicate[0];
}
}
}
@@ -0,0 +1,136 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.worldgen.WorldGenSurfaceStructure;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.WorldServer;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Copied from PositionTrigger and modified to work with wizardry's structures. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class StructureTrigger implements ICriterionTrigger<StructureTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, StructureTrigger.Listeners> listeners = Maps.newHashMap();
public StructureTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<StructureTrigger.Instance> listener){
StructureTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new StructureTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<StructureTrigger.Instance> listener){
StructureTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public StructureTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
return new StructureTrigger.Instance(this.id, JsonUtils.getString(json, "structure_type"));
}
public void trigger(EntityPlayerMP player){
StructureTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(player.getServerWorld(), player.posX, player.posY, player.posZ);
}
}
public static class Instance extends AbstractCriterionInstance {
private final WorldGenSurfaceStructure structureType;
public Instance(ResourceLocation criterionIn, String name){
super(criterionIn);
this.structureType = WorldGenSurfaceStructure.byName(name);
}
public boolean test(WorldServer world, double x, double y, double z){
return structureType.isInsideStructure(world, x, y, z);
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<StructureTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<StructureTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<StructureTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(WorldServer world, double x, double y, double z){
List<Listener<StructureTrigger.Instance>> list = null;
for(Listener<StructureTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(world, x, y, z)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<StructureTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -0,0 +1,96 @@
package electroblob.wizardry.api;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.util.SpellProperties;
import net.minecraft.util.text.Style;
import net.minecraftforge.common.util.EnumHelper;
/**
* This class contains methods similar to those in {@link EnumHelper} specific to wizardry's enum types.
*
* @author Electroblob
* @since Wizardry 4.2
*/
public final class WizardryEnumHelper {
// Make sure these are updated if the relevant constructors are updated!
// Can't we do some kind of reflection to access them and generate these arrays?
private static final Class[] TIER_ARGUMENTS = new Class[]{Integer.class, Integer.class, Integer.class, Style.class, String.class};
private static final Class[] ELEMENT_ARGUMENTS = new Class[]{Style.class, String.class, String.class};
private static final Class[] SPELL_TYPE_ARGUMENTS = new Class[]{String.class};
private static final Class[] SPELL_CONTEXT_ARGUMENTS = new Class[]{String.class};
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new tiers. Use this method in preference to the generic one in case the constructor
* parameters change for {@code Tier}.
* <p></p>
* As of version 4.2, wizardry now has partial support for externally-added tiers; you'll need to do some of the
* legwork yourself though.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param maxCharge The maximum charge for wands of this tier.
* @param upgradeLimit The maximum total number of special upgrades that can be applied to wands of this tier.
* @param weight The weight of this tier in the standard weighting.
* @param colour The colour of text associated with this tier, as a style object.
* @param name The unlocalised name of this tier, as used in translation keys.
* @return The resulting {@code Tier} enum constant.
*/
public static Tier addTier(String codeName, int maxCharge, int upgradeLimit, int weight, Style colour, String name){
return EnumHelper.addEnum(Tier.class, codeName, TIER_ARGUMENTS, maxCharge, upgradeLimit, weight, colour, name);
}
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new elements. Use this method in preference to the generic one in case the constructor
* parameters change for {@code Element}.
* <p></p>
* As of version 4.2, wizardry now has full support for externally-added elements.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param colour The colour of text associated with this element, as a style object.
* @param name The unlocalised name of this element, as used in translation keys.
* @param modID The mod ID of the mod that added this element, for icon rendering purposes.
* @return The resulting {@code Element} enum constant.
*/
// This is the only one that needs the mod ID argument because elements are the only ones that have icons
// For some reason Minecraft doesn't seem to care about the resource domain for lang files, it just pools them
public static Element addElement(String codeName, Style colour, String name, String modID){
return EnumHelper.addEnum(Element.class, codeName, ELEMENT_ARGUMENTS, colour, name, modID);
}
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new spell types. Use this method in preference to the generic one in case the constructor
* parameters change for {@code SpellType}.
* <p></p>
* As of version 4.2, wizardry now has full support for externally-added spell types.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param name The unlocalised name of this spell type, as used in translation keys.
* @return The resulting {@code SpellType} enum constant.
*/
public static SpellType addSpellType(String codeName, String name){
return EnumHelper.addEnum(SpellType.class, codeName, SPELL_TYPE_ARGUMENTS, name);
}
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new spell contexts (for use in spell property JSON files). Use this method in preference
* to the generic one in case the constructor parameters change for {@code Context}.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param name The identifier for this spell context, as used in the JSON file.
* @return The resulting {@code Context} enum constant.
*/
public static SpellProperties.Context addSpellContext(String codeName, String name){
return EnumHelper.addEnum(SpellProperties.Context.class, codeName, SPELL_CONTEXT_ARGUMENTS, name);
}
}
@@ -1,17 +1,14 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
@@ -55,6 +52,16 @@ public class BlockArcaneWorkbench extends BlockContainer {
return false;
}
@Override
public boolean isFullCube(IBlockState state){
return false;
}
@Override
public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){
return face == EnumFacing.DOWN ? BlockFaceShape.SOLID : BlockFaceShape.UNDEFINED;
}
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand,
EnumFacing side, float hitX, float hitY, float hitZ){
@@ -65,59 +72,20 @@ public class BlockArcaneWorkbench extends BlockContainer {
return false;
}
player.openGui(Wizardry.instance, WizardryGuiHandler.ARCANE_WORKBENCH, world, pos.getX(), pos.getY(),
pos.getZ());
player.openGui(Wizardry.instance, WizardryGuiHandler.ARCANE_WORKBENCH, world, pos.getX(), pos.getY(), pos.getZ());
return true;
}
@Override
public void breakBlock(World world, BlockPos pos, IBlockState block){
TileEntity tileentity = world.getTileEntity(pos);
Random random = new Random();
TileEntityArcaneWorkbench tileentityarcaneworkbench = (TileEntityArcaneWorkbench)world.getTileEntity(pos);
if(tileentity instanceof TileEntityArcaneWorkbench){
InventoryHelper.dropInventoryItems(world, pos, (TileEntityArcaneWorkbench)tileentity);
}
if(tileentityarcaneworkbench != null){
for(int j1 = 0; j1 < tileentityarcaneworkbench.getSizeInventory(); ++j1){
ItemStack itemstack = tileentityarcaneworkbench.getStackInSlot(j1);
if(!itemstack.isEmpty()){
float f = random.nextFloat() * 0.8F + 0.1F;
float f1 = random.nextFloat() * 0.8F + 0.1F;
EntityItem entityitem;
for(float f2 = random.nextFloat() * 0.8F + 0.1F; itemstack.getCount() > 0; world
.spawnEntity(entityitem)){
int k1 = random.nextInt(21) + 10;
if(k1 > itemstack.getCount()){
k1 = itemstack.getCount();
}
itemstack.shrink(k1);
entityitem = new EntityItem(world, (double)((float)pos.getX() + f),
(double)((float)pos.getY() + f1), (double)((float)pos.getZ() + f2),
new ItemStack(itemstack.getItem(), k1, itemstack.getItemDamage()));
float f3 = 0.05F;
entityitem.motionX = (double)((float)random.nextGaussian() * f3);
entityitem.motionY = (double)((float)random.nextGaussian() * f3 + 0.2F);
entityitem.motionZ = (double)((float)random.nextGaussian() * f3);
if(itemstack.hasTagCompound()){
entityitem.getItem().setTagCompound(((NBTTagCompound)itemstack.getTagCompound().copy()));
}
}
}
}
// par1World.func_96440_m(par2, par3, par4, block);
}
super.breakBlock(world, pos, block);
super.breakBlock(world, pos, block);
}
}
@@ -0,0 +1,76 @@
package electroblob.wizardry.block;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.block.Block;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import java.util.EnumMap;
public class BlockCrystal extends Block {
public static final PropertyEnum<Element> ELEMENT = PropertyEnum.create("element", Element.class);
private static final EnumMap<Element, MapColor> map_colours = new EnumMap<>(Element.class);
static {
map_colours.put(Element.MAGIC, MapColor.PINK);
map_colours.put(Element.FIRE, MapColor.ORANGE_STAINED_HARDENED_CLAY);
map_colours.put(Element.ICE, MapColor.LIGHT_BLUE);
map_colours.put(Element.LIGHTNING, MapColor.CYAN);
map_colours.put(Element.NECROMANCY, MapColor.PURPLE);
map_colours.put(Element.EARTH, MapColor.GREEN);
map_colours.put(Element.SORCERY, MapColor.LIME);
map_colours.put(Element.HEALING, MapColor.YELLOW);
}
public BlockCrystal(Material material){
super(material);
this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.MAGIC));
this.setCreativeTab(WizardryTabs.WIZARDRY);
this.setHarvestLevel("pickaxe", 2);
}
@Override
public int damageDropped(IBlockState state){
return (state.getValue(ELEMENT)).ordinal();
}
@Override
public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){
return map_colours.get(state.getProperties().get(ELEMENT));
}
@Override
public void getSubBlocks(CreativeTabs tab, NonNullList<ItemStack> items){
if(this.getCreativeTab() == tab){
for(Element element : Element.values()){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
@Override
public IBlockState getStateFromMeta(int metadata){
return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]);
}
@Override
public int getMetaFromState(IBlockState state){
return (state.getValue(ELEMENT)).ordinal();
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, ELEMENT);
}
}
@@ -1,10 +1,8 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.BlockBush;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
@@ -19,6 +17,8 @@ import net.minecraftforge.event.entity.player.BonemealEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.Random;
// Extending BlockBush allows me to remove nearly everything from this class.
@Mod.EventBusSubscriber
public class BlockCrystalFlower extends BlockBush {
@@ -41,10 +41,10 @@ public class BlockCrystalFlower extends BlockBush {
@Override
public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random){
if(world.isRemote && random.nextBoolean()){
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, pos.getX() + random.nextDouble(),
pos.getY() + random.nextDouble() / 2 + 0.5, pos.getZ() + random.nextDouble(), 0d, 0.01, 0d,
20 + random.nextInt(10), 0.5f + (random.nextFloat() / 2), 0.5f + (random.nextFloat() / 2),
0.5f + (random.nextFloat() / 2));
ParticleBuilder.create(Type.SPARKLE)
.pos(pos.getX() + random.nextDouble(), pos.getY() + random.nextDouble() / 2 + 0.5, pos.getZ() + random.nextDouble()).vel(0, 0.01, 0)
.time(20 + random.nextInt(10)).clr(0.5f + (random.nextFloat() / 2), 0.5f + (random.nextFloat() / 2),
0.5f + (random.nextFloat() / 2)).spawn(world);
}
}
@@ -1,13 +1,17 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.block.Block;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.item.Item;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import java.util.Random;
public class BlockCrystalOre extends Block {
@@ -20,10 +24,17 @@ public class BlockCrystalOre extends Block {
@Override
public int quantityDropped(IBlockState state, int fortune, Random random){
// This now works the same way as vanilla ores
if(fortune > 0){
return random.nextInt(2) + 1 + random.nextInt(fortune);
int i = random.nextInt(fortune + 2) - 1;
if(i < 0) i = 0;
return (random.nextInt(3) + 1) * (i + 1);
}else{
return random.nextInt(2) + 1;
return random.nextInt(3) + 1;
}
}
@@ -31,4 +42,16 @@ public class BlockCrystalOre extends Block {
public Item getItemDropped(IBlockState state, Random random, int fortune){
return WizardryItems.magic_crystal;
}
@Override
public int getExpDrop(IBlockState state, IBlockAccess world, BlockPos pos, int fortune){
Random rand = world instanceof World ? ((World)world).rand : RANDOM;
if(this.getItemDropped(state, rand, fortune) != Item.getItemFromBlock(this)){
return MathHelper.getInt(rand, 1, 4);
}
return 0;
}
}
@@ -0,0 +1,28 @@
package electroblob.wizardry.block;
import net.minecraft.block.BlockFrostedIce;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.Random;
/** Like {@link BlockFrostedIce}, but melting does not depend on light level or neighbouring blocks, and it just
* disappears instead of turning to water. */
public class BlockDryFrostedIce extends BlockFrostedIce {
@Override
protected void turnIntoWater(World world, BlockPos pos){
world.destroyBlock(pos, false);
}
@Override
public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand){
if(rand.nextInt(3) == 0){
this.slightlyMelt(worldIn, pos, state, rand, true);
}else{
worldIn.scheduleUpdate(pos, this, MathHelper.getInt(rand, 20, 40));
}
}
}
@@ -1,23 +1,31 @@
package electroblob.wizardry.block;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityMagicLight;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
public class BlockMagicLight extends BlockContainer {
public class BlockMagicLight extends Block implements ITileEntityProvider {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
//private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
public BlockMagicLight(Material par2Material){
super(par2Material);
public BlockMagicLight(Material material){
super(material);
this.setLightLevel(1.0f);
this.setBlockUnbreakable();
}
@Override
@@ -28,13 +36,38 @@ public class BlockMagicLight extends BlockContainer {
}
@Override
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
return AABB;
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){
// Let the player dispel any lights if they have the lantern charm, not just the permanent ones because that would be annoying!
if(player.getHeldItem(hand).getItem() instanceof ISpellCastingItem && ItemArtefact.isArtefactActive(player, WizardryItems.charm_light)){
world.setBlockToAir(pos);
return true;
}else{
return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ);
}
}
// @Override
// public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
// return AABB;
// }
@Override
public boolean isCollidable(){
return false;
// This method has nothing to do with entity movement, it's just for raytracing
return true;
}
@Override
public boolean addDestroyEffects(World world, BlockPos pos, net.minecraft.client.particle.ParticleManager manager){
if(world.getBlockState(pos).getBlock() == this) return true; // No break particles!
else return super.addDestroyEffects(world, pos, manager);
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
@@ -0,0 +1,116 @@
package electroblob.wizardry.block;
import net.minecraft.block.Block;
import net.minecraft.block.BlockObsidian;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.Random;
/** Like {@link net.minecraft.block.BlockFrostedIce}, but for lava instead of water. */
// This is mostly copied from that class, with a few changes
public class BlockObsidianCrust extends BlockObsidian {
public static final PropertyInteger AGE = PropertyInteger.create("age", 0, 3);
public BlockObsidianCrust(){
this.setDefaultState(this.blockState.getBaseState().withProperty(AGE, 0));
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(AGE);
}
@Override
public IBlockState getStateFromMeta(int meta){
return this.getDefaultState().withProperty(AGE, MathHelper.clamp(meta, 0, 3));
}
@Override
public void updateTick(World world, BlockPos pos, IBlockState state, Random random){
if((random.nextInt(3) == 0 || this.countNeighbors(world, pos) < 4) && world.getLightFromNeighbors(pos) > 11 - state.getValue(AGE) - state.getLightOpacity()){
this.slightlyMelt(world, pos, state, random, true);
}else{
world.scheduleUpdate(pos, this, MathHelper.getInt(random, 20, 40));
}
}
@Override
public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos fromPos){
if(block == this){
int i = this.countNeighbors(world, pos);
if(i < 2){
this.melt(world, pos);
}
}
}
private int countNeighbors(World world, BlockPos pos){
int i = 0;
for(EnumFacing enumfacing : EnumFacing.values()){
if(world.getBlockState(pos.offset(enumfacing)).getBlock() == this){
++i;
if(i >= 4){
return i;
}
}
}
return i;
}
protected void slightlyMelt(World world, BlockPos pos, IBlockState state, Random random, boolean meltNeighbours){
int i = state.getValue(AGE);
if(i < 3){
world.setBlockState(pos, state.withProperty(AGE, i + 1), 2);
world.scheduleUpdate(pos, this, MathHelper.getInt(random, 20, 40));
}else{
this.melt(world, pos);
if(meltNeighbours){
for(EnumFacing enumfacing : EnumFacing.values()){
BlockPos blockpos = pos.offset(enumfacing);
IBlockState iblockstate = world.getBlockState(blockpos);
if(iblockstate.getBlock() == this){
this.slightlyMelt(world, blockpos, iblockstate, random, false);
}
}
}
}
}
protected void melt(World world, BlockPos pos){
world.setBlockState(pos, Blocks.LAVA.getDefaultState());
world.neighborChanged(pos, Blocks.LAVA, pos);
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, AGE);
}
@Override
public ItemStack getItem(World world, BlockPos pos, IBlockState state){
return ItemStack.EMPTY;
}
}
@@ -0,0 +1,123 @@
package electroblob.wizardry.block;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.Explosion;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.EnumMap;
public class BlockPedestal extends Block implements ITileEntityProvider {
public static final PropertyEnum<Element> ELEMENT = PropertyEnum.create("element", Element.class,
Arrays.copyOfRange(Element.values(), 1, Element.values().length)); // Everything except MAGIC
// A 'natural' pedestal is one that was generated as part of a structure, is unbreakable and has a tileentity
public static final PropertyBool NATURAL = PropertyBool.create("natural");
private static final EnumMap<Element, MapColor> map_colours = new EnumMap<>(Element.class);
static {
map_colours.put(Element.FIRE, MapColor.RED_STAINED_HARDENED_CLAY);
map_colours.put(Element.ICE, MapColor.LIGHT_BLUE_STAINED_HARDENED_CLAY);
map_colours.put(Element.LIGHTNING, MapColor.CYAN_STAINED_HARDENED_CLAY);
map_colours.put(Element.NECROMANCY, MapColor.PURPLE_STAINED_HARDENED_CLAY);
map_colours.put(Element.EARTH, MapColor.BROWN_STAINED_HARDENED_CLAY);
map_colours.put(Element.SORCERY, MapColor.GRAY);
map_colours.put(Element.HEALING, MapColor.YELLOW_STAINED_HARDENED_CLAY);
}
public BlockPedestal(Material material){
super(material);
this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.FIRE).withProperty(NATURAL, false));
this.setCreativeTab(WizardryTabs.WIZARDRY);
this.setHardness(1.5F);
this.setResistance(10.0F);
}
@Override
public int damageDropped(IBlockState state){
return state.getValue(ELEMENT).ordinal(); // Ignore the NATURAL state here, it's unobtainable
}
@Override
public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){
return map_colours.get(state.getProperties().get(ELEMENT));
}
@Override
public void getSubBlocks(CreativeTabs tab, NonNullList<ItemStack> items){
// Ignore the NATURAL state here, it's unobtainable
if(this.getCreativeTab() == tab){
for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
@Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others
}
@Override
public float getBlockHardness(IBlockState state, World world, BlockPos pos){
return state.getValue(NATURAL) ? -1 : super.getBlockHardness(state, world, pos);
}
@Override
public float getExplosionResistance(World world, BlockPos pos, @Nullable Entity exploder, Explosion explosion){
return world.getBlockState(pos).getValue(NATURAL) ? 6000000.0F : super.getExplosionResistance(world, pos, exploder, explosion);
}
@Override
public boolean hasTileEntity(IBlockState state){
return state.getValue(NATURAL); // Only naturally-generated pedestals have a (shrine core) tile entity
}
@Nullable
@Override
public TileEntity createNewTileEntity(World world, int meta){
return new TileEntityShrineCore();
}
@Override
public IBlockState getStateFromMeta(int metadata){
boolean natural = false;
if(metadata > ELEMENT.getAllowedValues().size()){
natural = true;
metadata -= ELEMENT.getAllowedValues().size();
}
return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]).withProperty(NATURAL, natural);
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(ELEMENT).ordinal() + (state.getValue(NATURAL) ? ELEMENT.getAllowedValues().size() : 0);
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, ELEMENT, NATURAL);
}
}
@@ -0,0 +1,85 @@
package electroblob.wizardry.block;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.block.Block;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import java.util.Arrays;
import java.util.EnumMap;
public class BlockRunestone extends Block {
public static final PropertyEnum<Element> ELEMENT = PropertyEnum.create("element", Element.class,
Arrays.copyOfRange(Element.values(), 1, Element.values().length)); // Everything except MAGIC
private static final EnumMap<Element, MapColor> map_colours = new EnumMap<>(Element.class);
static {
map_colours.put(Element.FIRE, MapColor.RED_STAINED_HARDENED_CLAY);
map_colours.put(Element.ICE, MapColor.LIGHT_BLUE_STAINED_HARDENED_CLAY);
map_colours.put(Element.LIGHTNING, MapColor.CYAN_STAINED_HARDENED_CLAY);
map_colours.put(Element.NECROMANCY, MapColor.PURPLE_STAINED_HARDENED_CLAY);
map_colours.put(Element.EARTH, MapColor.BROWN_STAINED_HARDENED_CLAY);
map_colours.put(Element.SORCERY, MapColor.GRAY);
map_colours.put(Element.HEALING, MapColor.YELLOW_STAINED_HARDENED_CLAY);
}
public BlockRunestone(Material material){
super(material);
this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.FIRE));
this.setCreativeTab(WizardryTabs.WIZARDRY);
this.setHardness(1.5F);
this.setResistance(10.0F);
}
@Override
public int damageDropped(IBlockState state){
return state.getValue(ELEMENT).ordinal();
}
@Override
public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){
return map_colours.get(state.getProperties().get(ELEMENT));
}
@Override
public void getSubBlocks(CreativeTabs tab, NonNullList<ItemStack> items){
if(this.getCreativeTab() == tab){
for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
@Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others
}
@Override
public IBlockState getStateFromMeta(int metadata){
return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]);
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(ELEMENT).ordinal();
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, ELEMENT);
}
}
@@ -1,13 +1,13 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityPlayerSave;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -25,8 +25,9 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
// TODO: Apparently you shouldn't extend BlockContainer. I feel like BlockArcaneWorkbench should, but what about the rest?
public class BlockSnare extends BlockContainer {
import java.util.Random;
public class BlockSnare extends Block implements ITileEntityProvider {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0f, 0.0f, 0.0f, 1.0f, 0.0625f, 1.0f);
@@ -51,17 +52,21 @@ public class BlockSnare extends BlockContainer {
}
@Override
public void onEntityCollidedWithBlock(World world, BlockPos pos, IBlockState state, Entity entity){
public void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){
if(!world.isRemote && entity instanceof EntityLivingBase){
if(world.getTileEntity(pos) instanceof TileEntityPlayerSave){
TileEntityPlayerSave tileentity = (TileEntityPlayerSave)world.getTileEntity(pos);
if(WizardryUtilities.isValidTarget(tileentity.getCaster(), entity)){
((EntityLivingBase)entity).attackEntityFrom(
MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC), 6);
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 100, 2));
if(AllyDesignationSystem.isValidTarget(tileentity.getCaster(), entity)){
entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC),
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()));
world.destroyBlock(pos, false);
}
@@ -80,7 +85,7 @@ public class BlockSnare extends BlockContainer {
}
@Override
public BlockRenderLayer getBlockLayer(){
public BlockRenderLayer getRenderLayer() {
return BlockRenderLayer.CUTOUT;
}
@@ -1,13 +1,11 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -24,10 +22,10 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on
// block break.
import java.util.Random;
@Mod.EventBusSubscriber
public class BlockSpectral extends BlockContainer {
public class BlockSpectral extends Block implements ITileEntityProvider {
public BlockSpectral(Material material){
super(material);
@@ -36,7 +34,7 @@ public class BlockSpectral extends BlockContainer {
// Replaces getRenderBlockPass
@Override
public BlockRenderLayer getBlockLayer(){
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.TRANSLUCENT;
}
@@ -45,10 +43,8 @@ public class BlockSpectral extends BlockContainer {
return EnumBlockRenderType.MODEL;
}
// Apparently it's OK to override this, despite it being deprecated. More
// importantly, it being deprecated is not
// Forge's doing, rather it is Mojang themselves misusing the @Deprecated
// annotation to mean 'internal, don't call'.
// Apparently it's OK to override this, despite it being deprecated. More importantly, it being deprecated is not
// Forge's doing, rather it is Mojang themselves misusing the @Deprecated annotation to mean 'internal, don't call'.
@Override
public boolean isOpaqueCube(IBlockState state){
return false;
@@ -61,18 +57,14 @@ public class BlockSpectral extends BlockContainer {
@Override
public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random){
// Middle of block
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, pos.getX() + random.nextDouble(),
pos.getY() + random.nextDouble(), pos.getZ() + random.nextDouble(), 0, 0, 0,
(int)(16.0D / (Math.random() * 0.8D + 0.2D)), 0.4f + random.nextFloat() * 0.2f,
0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f);
// Top surface
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, pos.getX() + random.nextDouble(), pos.getY() + 1,
pos.getZ() + random.nextDouble(), 0, 0, 0, (int)(16.0D / (Math.random() * 0.8D + 0.2D)),
0.4f + random.nextFloat() * 0.2f, 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f);
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, pos.getX() + random.nextDouble(), pos.getY() + 1,
pos.getZ() + random.nextDouble(), 0, 0, 0, (int)(16.0D / (Math.random() * 0.8D + 0.2D)),
0.4f + random.nextFloat() * 0.2f, 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f);
for(int i=0; i<2; i++){
ParticleBuilder.create(Type.DUST)
.pos(pos.getX() + random.nextDouble(), pos.getY() + random.nextDouble(), pos.getZ() + random.nextDouble())
.time((int)(16.0D / (Math.random() * 0.8D + 0.2D)))
.clr(0.4f + random.nextFloat() * 0.2f, 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f)
.shaded(true).spawn(world);
}
}
// Overriden to make the block always look full brightness despite not emitting
@@ -82,6 +74,11 @@ public class BlockSpectral extends BlockContainer {
return 15;
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityTimer(1200);
@@ -1,14 +1,13 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.spell.Petrify;
import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLiving;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumBlockRenderType;
@@ -20,10 +19,17 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public class BlockStatue extends BlockContainer {
import java.util.Random;
public class BlockStatue extends Block implements ITileEntityProvider {
private boolean isIce;
/** The NBT tag name for storing the petrified flag (used for rendering) in the target's tag compound. */
public static final String PETRIFIED_NBT_KEY = "petrified";
/** The NBT tag name for storing the frozen flag (used for rendering) in the target's tag compound. */
public static final String FROZEN_NBT_KEY = "frozen";
public BlockStatue(Material material){
super(material);
this.isIce = material == Material.ICE;
@@ -97,7 +103,7 @@ public class BlockStatue extends BlockContainer {
}
@Override
public BlockRenderLayer getBlockLayer(){
public BlockRenderLayer getRenderLayer(){
return this.isIce ? BlockRenderLayer.TRANSLUCENT : BlockRenderLayer.SOLID;
}
@@ -106,6 +112,11 @@ public class BlockStatue extends BlockContainer {
return this.isIce ? EnumBlockRenderType.MODEL : EnumBlockRenderType.ENTITYBLOCK_ANIMATED;
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityStatue(this.isIce);
@@ -146,7 +157,7 @@ public class BlockStatue extends BlockContainer {
// This is only when position == 1 because world.destroyBlock calls this function for the other blocks.
if(tileentity != null && tileentity.position == 1 && tileentity.creature != null){
tileentity.creature.getEntityData().removeTag(Petrify.NBT_KEY);
tileentity.creature.getEntityData().removeTag(BlockStatue.PETRIFIED_NBT_KEY);
tileentity.creature.isDead = false;
world.spawnEntity(tileentity.creature);
}
@@ -166,4 +177,84 @@ public class BlockStatue extends BlockContainer {
return this.isIce && block == this ? false : super.shouldSideBeRendered(blockState, blockAccess, pos, side);
}
/**
* Turns the given entity into a statue. The type of statue depends on the block instance this method was invoked on.
* @param entity The entity to turn into a statue.
* @param duration The time for which the entity should remain a statue. For petrified creatures, this is the minimum
* time it can stay as a statue.
* @return True if the entity was successfully turned into a statue, false if not (i.e. something was in the way).
*/
// Making this an instance method means it works equally well for both types of statue
public boolean convertToStatue(EntityLiving entity, int duration){
if(entity.deathTime > 0) return false;
BlockPos pos = new BlockPos(entity);
World world = entity.world;
entity.hurtTime = 0; // Stops the entity looking red while frozen and the resulting z-fighting
entity.extinguish();
// Short mobs such as spiders and pigs
if((entity.height < 1.2 || entity.isChild()) && WizardryUtilities.canBlockBeReplaced(world, pos)){
world.setBlockState(pos, this.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(entity, 1, 1);
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration);
}
entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
entity.setDead();
return true;
}
// Normal sized mobs like zombies and skeletons
else if(entity.height < 2.5 && WizardryUtilities.canBlockBeReplaced(world, pos)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())){
world.setBlockState(pos, this.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(entity, 1, 2);
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration);
}
world.setBlockState(pos.up(), this.getDefaultState());
if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(entity, 2, 2);
}
entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
entity.setDead();
return true;
}
// Tall mobs like endermen
else if(WizardryUtilities.canBlockBeReplaced(world, pos)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())
&& WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){
world.setBlockState(pos, this.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(entity, 1, 3);
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration);
}
world.setBlockState(pos.up(), this.getDefaultState());
if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(entity, 2, 3);
}
world.setBlockState(pos.up(2), this.getDefaultState());
if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up(2))).setCreatureAndPart(entity, 3, 3);
}
entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
entity.setDead();
return true;
}
return false;
}
}
@@ -0,0 +1,181 @@
package electroblob.wizardry.block;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityPlayerSaveTimed;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage;
import net.minecraft.block.*;
import net.minecraft.block.BlockDoublePlant.EnumBlockHalf;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockStateContainer;
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.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.Random;
@Mod.EventBusSubscriber
public class BlockThorns extends BlockBush implements ITileEntityProvider {
public static final int GROWTH_STAGES = 8;
public static final PropertyInteger AGE = PropertyInteger.create("age", 0, GROWTH_STAGES-1);
public static final PropertyEnum<EnumBlockHalf> HALF = PropertyEnum.create("half", EnumBlockHalf.class);
public BlockThorns(){
this.setDefaultState(this.blockState.getBaseState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(AGE, 7));
this.setHardness(4);
this.setSoundType(SoundType.PLANT);
this.setCreativeTab(null);
}
@Override
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
return FULL_BLOCK_AABB;
}
@Override
public IBlockState getStateFromMeta(int meta){
return this.getDefaultState().withProperty(HALF, EnumBlockHalf.values()[meta / GROWTH_STAGES]).withProperty(AGE, meta % GROWTH_STAGES);
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(HALF).ordinal() * GROWTH_STAGES + state.getValue(AGE);
}
// @Override
// public void updateTick(World world, BlockPos pos, IBlockState state, Random rand){
//
// super.updateTick(world, pos, state, rand);
//
// // Update the state, including on the client, but don't do a block update since it's only visual
// if(state.getValue(AGE) < GROWTH_STAGES-1) world.setBlockState(pos, state.withProperty(AGE, state.getValue(AGE) + 1), 2);
// }
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, HALF, AGE);
}
public void placeAt(World world, BlockPos lowerPos, int flags){
world.setBlockState(lowerPos, this.getDefaultState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(AGE, 0), flags);
world.setBlockState(lowerPos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER).withProperty(AGE, 0), flags);
}
@Override
public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack){
world.setBlockState(pos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER), 2);
}
@Override
public void breakBlock(World world, BlockPos pos, IBlockState state){
super.breakBlock(world, pos, state);
if(state.getValue(HALF) == EnumBlockHalf.LOWER){
if(world.getBlockState(pos.up()).getBlock() == this){
world.destroyBlock(pos.up(), false);
}
}else{
if(world.getBlockState(pos.down()).getBlock() == this){
world.destroyBlock(pos.down(), false);
}
}
}
public boolean canBlockStay(World worldIn, BlockPos pos, IBlockState state){
if(state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER){
return worldIn.getBlockState(pos.down()).getBlock() == this;
}else{
IBlockState iblockstate = worldIn.getBlockState(pos.up());
return iblockstate.getBlock() == this && this.canSustainBush(worldIn.getBlockState(pos.down()));
}
}
// @Override
// public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos){
// // Copied from BlockFlowerPot on authority of the Forge docs, which says this check is necessary
// SoundLoopSpellDispenser tileentity = world instanceof ChunkCache ? ((ChunkCache)world).getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK) : world.getTileEntity(pos);
//
// if(tileentity instanceof TileEntityPlayerSaveTimed){
// state = state.withProperty(AGE, Math.min(7, ((TileEntityPlayerSaveTimed)tileentity).timer/2));
// }else{
// state = state.withProperty(AGE, 7);
// }
//
// return state;
// }
@Override
public void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){
if(!world.isRemote){
if(applyThornDamage(world, pos, entity)){
entity.setInWeb();
}
}
}
private static boolean applyThornDamage(World world, BlockPos pos, Entity target){
DamageSource source = DamageSource.CACTUS;
TileEntity tileentity = world.getTileEntity(pos);
if(tileentity instanceof TileEntityPlayerSaveTimed){
if(AllyDesignationSystem.isValidTarget(((TileEntityPlayerSaveTimed)tileentity).getCaster(), target)){
source = MagicDamage.causeDirectMagicDamage(((TileEntityPlayerSaveTimed)tileentity).getCaster(),
MagicDamage.DamageType.MAGIC);
}else{
return false; // Don't attack or slow allies of the caster
}
}
if(world.getTotalWorldTime() % 20 == 0) target.attackEntityFrom(source, Spells.forest_of_thorns.getProperty(Spell.DAMAGE).floatValue());
return true;
}
@Override
public Block.EnumOffsetType getOffsetType(){
return Block.EnumOffsetType.XZ;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityPlayerSaveTimed(600);
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override public boolean isReplaceable(IBlockAccess world, BlockPos pos){ return false; }
@Override protected boolean canSustainBush(IBlockState state){ return state.isNormalCube(); }
@Override public Item getItemDropped(IBlockState state, Random rand, int fortune){ return Items.AIR; }
@Override public boolean canSilkHarvest(World world, BlockPos pos, IBlockState state, EntityPlayer player){ return false; }
@SubscribeEvent
public static void onLeftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event){
if(!event.getWorld().isRemote && event.getWorld().getTotalWorldTime() % 20 == 0
&& event.getWorld().getBlockState(event.getPos()).getBlock() == WizardryBlocks.thorns){
applyThornDamage(event.getWorld(), event.getPos(), event.getEntity());
}
}
}
@@ -1,12 +1,16 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Transportation;
import electroblob.wizardry.util.Location;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -20,6 +24,10 @@ import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class BlockTransportationStone extends Block {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0625f * 5, 0, 0.0625f * 5, 0.0625f * 11, 0.0625f * 6,
@@ -65,15 +73,21 @@ public class BlockTransportationStone extends Block {
public boolean isOpaqueCube(IBlockState state){
return false;
}
@Override
public void onNeighborChange(IBlockAccess world, BlockPos pos, BlockPos neighbor){
public boolean isSideSolid(IBlockState base_state, IBlockAccess world, BlockPos pos, EnumFacing side){
return side == EnumFacing.DOWN;
}
super.onNeighborChange(world, pos, neighbor);
@SuppressWarnings("deprecation")
@Override
public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos fromPos){
if(!world.isSideSolid(pos.down(), EnumFacing.UP, false) && world instanceof World){
this.dropBlockAsItem((World)world, pos, world.getBlockState(pos), 0);
((World)world).setBlockToAir(pos);
super.neighborChanged(state, world, pos, block, fromPos);
if(!world.isSideSolid(pos.down(), EnumFacing.UP, false)){
this.dropBlockAsItem(world, pos, world.getBlockState(pos), 0);
world.setBlockToAir(pos);
}
}
@@ -97,7 +111,7 @@ public class BlockTransportationStone extends Block {
ItemStack stack = player.getHeldItem(hand);
if(stack.getItem() instanceof ItemWand){
if(stack.getItem() instanceof ISpellCastingItem){
if(WizardData.get(player) != null){
WizardData data = WizardData.get(player);
@@ -106,17 +120,59 @@ public class BlockTransportationStone extends Block {
for(int z = -1; z <= 1; z++){
BlockPos pos1 = pos.add(x, 0, z);
if(testForCircle(world, pos1)){
data.setStoneCircleLocation(pos1, world.provider.getDimension());
if(!world.isRemote) player.sendMessage(
new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm",
Spells.transportation.getNameForTranslationFormatted()));
Location here = new Location(pos1, player.dimension);
List<Location> locations = data.getVariable(Transportation.LOCATIONS_KEY);
if(locations == null) data.setVariable(Transportation.LOCATIONS_KEY, locations = new ArrayList<>(Transportation.MAX_REMEMBERED_LOCATIONS));
if(ItemArtefact.isArtefactActive(player, WizardryItems.charm_transportation)){
if(locations.contains(here)){
locations.remove(here);
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.forget", here.pos.getX(), here.pos.getY(), here.pos.getZ(), here.dimension), true);
}else{
locations.add(here);
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.remember", here.pos.getX(), here.pos.getY(), here.pos.getZ(), here.dimension), true);
if(locations.size() > Transportation.MAX_REMEMBERED_LOCATIONS){
Location removed = locations.remove(0);
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.forget", removed.pos.getX(), removed.pos.getY(), removed.pos.getZ(), removed.dimension), true);
}
}
}else{
if(locations.isEmpty()) locations.add(here);
else{
locations.remove(here); // Prevents duplicates
locations.set(locations.size() - 1, here);
}
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm", Spells.transportation.getNameForTranslationFormatted()), true);
}
return true;
}
}
}
if(!world.isRemote)
player.sendMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid"));
if(!world.isRemote){
player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid"), true);
}else{
BlockPos centre = findMostLikelyCircle(world, pos);
// Displays particles in the required shape
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
ParticleBuilder.create(ParticleBuilder.Type.PATH)
.pos(WizardryUtilities.getCentre(centre).add(x, -0.3125, z)).clr(0x86ff65)
.time(200).scale(2).spawn(world);
}
}
}
return true;
}
}
@@ -130,12 +186,47 @@ public class BlockTransportationStone extends Block {
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
if(world.getBlockState(pos.add(x, 0, z)).getBlock() != WizardryBlocks.transportation_stone){
if(x != 0 || z != 0) return false;
return false;
}
}
}
return true;
}
private static BlockPos findMostLikelyCircle(World world, BlockPos pos){
int bestSoFar = 0;
BlockPos result = null;
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
BlockPos pos1 = pos.add(x, 0, z);
int n = getCircleCompleteness(world, pos1);
if(n > bestSoFar){
bestSoFar = n;
result = pos1;
}
}
}
return result;
}
private static int getCircleCompleteness(World world, BlockPos pos){
int n = 0;
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
if(world.getBlockState(pos.add(x, 0, z)).getBlock() == WizardryBlocks.transportation_stone) n++;
}
}
return n;
}
}
@@ -1,9 +1,8 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.tileentity.TileEntityTimer;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
@@ -17,15 +16,17 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on block break.
public class BlockVanishingCobweb extends BlockContainer {
import java.util.Random;
public class BlockVanishingCobweb extends Block implements ITileEntityProvider {
public BlockVanishingCobweb(Material material){
super(material);
}
@SideOnly(Side.CLIENT)
public BlockRenderLayer getBlockLayer(){
@Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT;
}
@@ -49,13 +50,18 @@ public class BlockVanishingCobweb extends BlockContainer {
return false;
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityTimer(400);
}
@Override
public void onEntityCollidedWithBlock(World world, BlockPos pos, IBlockState state, Entity entity){
public void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){
entity.setInWeb();
}
@@ -1,147 +1,90 @@
package electroblob.wizardry.client;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.CommonProxy;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.audio.MovingSoundEntity;
import electroblob.wizardry.client.audio.SoundLoop;
import electroblob.wizardry.client.audio.SoundLoopSpell;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.client.gui.config.NamedBooleanEntry;
import electroblob.wizardry.client.gui.config.SpellHUDSkinChooserEntry;
import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook;
import electroblob.wizardry.client.model.ModelWizardArmour;
import electroblob.wizardry.client.particle.ParticleBlizzard;
import electroblob.wizardry.client.particle.ParticleDarkMagic;
import electroblob.wizardry.client.particle.ParticleDust;
import electroblob.wizardry.client.particle.ParticleGiantBubble;
import electroblob.wizardry.client.particle.ParticleIce;
import electroblob.wizardry.client.particle.ParticleLeaf;
import electroblob.wizardry.client.particle.ParticleMagicFlame;
import electroblob.wizardry.client.particle.ParticlePath;
import electroblob.wizardry.client.particle.ParticleRotatingSparkle;
import electroblob.wizardry.client.particle.ParticleSnow;
import electroblob.wizardry.client.particle.ParticleSpark;
import electroblob.wizardry.client.particle.ParticleSparkle;
import electroblob.wizardry.client.particle.ParticleTornado;
import electroblob.wizardry.client.renderer.LayerStone;
import electroblob.wizardry.client.renderer.RenderArc;
import electroblob.wizardry.client.renderer.RenderArcaneWorkbench;
import electroblob.wizardry.client.renderer.RenderBlackHole;
import electroblob.wizardry.client.renderer.RenderBlank;
import electroblob.wizardry.client.renderer.RenderBubble;
import electroblob.wizardry.client.renderer.RenderDecay;
import electroblob.wizardry.client.renderer.RenderDecoy;
import electroblob.wizardry.client.renderer.RenderEvilWizard;
import electroblob.wizardry.client.renderer.RenderFireRing;
import electroblob.wizardry.client.renderer.RenderForceArrow;
import electroblob.wizardry.client.renderer.RenderHammer;
import electroblob.wizardry.client.renderer.RenderIceGiant;
import electroblob.wizardry.client.renderer.RenderIceSpike;
import electroblob.wizardry.client.renderer.RenderLightningDisc;
import electroblob.wizardry.client.renderer.RenderLightningPulse;
import electroblob.wizardry.client.renderer.RenderMagicArrow;
import electroblob.wizardry.client.renderer.RenderMagicLight;
import electroblob.wizardry.client.renderer.RenderPhoenix;
import electroblob.wizardry.client.renderer.RenderProjectile;
import electroblob.wizardry.client.renderer.RenderSigil;
import electroblob.wizardry.client.renderer.RenderSpiritHorse;
import electroblob.wizardry.client.renderer.RenderSpiritWolf;
import electroblob.wizardry.client.renderer.RenderStatue;
import electroblob.wizardry.client.renderer.RenderWizard;
import electroblob.wizardry.entity.EntityArc;
import electroblob.wizardry.client.particle.*;
import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory;
import electroblob.wizardry.client.renderer.*;
import electroblob.wizardry.command.SpellEmitter;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.EntityShield;
import electroblob.wizardry.entity.construct.EntityArrowRain;
import electroblob.wizardry.entity.construct.EntityBlackHole;
import electroblob.wizardry.entity.construct.EntityBlizzard;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.entity.construct.EntityDecay;
import electroblob.wizardry.entity.construct.EntityEarthquake;
import electroblob.wizardry.entity.construct.EntityFireRing;
import electroblob.wizardry.entity.construct.EntityFireSigil;
import electroblob.wizardry.entity.construct.EntityForcefield;
import electroblob.wizardry.entity.construct.EntityFrostSigil;
import electroblob.wizardry.entity.construct.EntityHailstorm;
import electroblob.wizardry.entity.construct.EntityHammer;
import electroblob.wizardry.entity.construct.EntityHealAura;
import electroblob.wizardry.entity.construct.EntityIceSpike;
import electroblob.wizardry.entity.construct.EntityLightningPulse;
import electroblob.wizardry.entity.construct.EntityLightningSigil;
import electroblob.wizardry.entity.construct.EntityTornado;
import electroblob.wizardry.entity.living.EntityDecoy;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityIceGiant;
import electroblob.wizardry.entity.living.EntityIceWraith;
import electroblob.wizardry.entity.living.EntityLightningWraith;
import electroblob.wizardry.entity.living.EntityPhoenix;
import electroblob.wizardry.entity.living.EntityShadowWraith;
import electroblob.wizardry.entity.living.EntitySpiritHorse;
import electroblob.wizardry.entity.living.EntitySpiritWolf;
import electroblob.wizardry.entity.living.EntityStormElemental;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.entity.projectile.EntityDarknessOrb;
import electroblob.wizardry.entity.projectile.EntityDart;
import electroblob.wizardry.entity.projectile.EntityFirebolt;
import electroblob.wizardry.entity.projectile.EntityFirebomb;
import electroblob.wizardry.entity.projectile.EntityForceArrow;
import electroblob.wizardry.entity.projectile.EntityForceOrb;
import electroblob.wizardry.entity.projectile.EntityIceCharge;
import electroblob.wizardry.entity.projectile.EntityIceLance;
import electroblob.wizardry.entity.projectile.EntityIceShard;
import electroblob.wizardry.entity.projectile.EntityLightningArrow;
import electroblob.wizardry.entity.projectile.EntityLightningDisc;
import electroblob.wizardry.entity.projectile.EntityMagicMissile;
import electroblob.wizardry.entity.projectile.EntityPoisonBomb;
import electroblob.wizardry.entity.projectile.EntitySmokeBomb;
import electroblob.wizardry.entity.projectile.EntitySpark;
import electroblob.wizardry.entity.projectile.EntitySparkBomb;
import electroblob.wizardry.entity.projectile.EntityThunderbolt;
import electroblob.wizardry.entity.construct.*;
import electroblob.wizardry.entity.living.*;
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.ItemScroll;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.PacketNPCCastSpell;
import electroblob.wizardry.packet.PacketPlayerSync.Message;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.packet.*;
import electroblob.wizardry.potion.PotionSlowTime;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Clairvoyance;
import electroblob.wizardry.spell.None;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.*;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityMagicLight;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryParticleType;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiMerchant;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.RenderBlaze;
import net.minecraft.client.renderer.entity.RenderHusk;
import net.minecraft.client.renderer.entity.RenderSkeleton;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ContainerMerchant;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.village.MerchantRecipeList;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import org.lwjgl.input.Keyboard;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The client proxy for wizardry.
@@ -154,6 +97,9 @@ public class ClientProxy extends CommonProxy {
/** Static instance of the mixed font renderer */
public static MixedFontRenderer mixedFontRenderer;
/** Static particle factory map */
private static final Map<ResourceLocation, IWizardryParticleFactory> factories = new HashMap<>();
// Key Bindings
public static final KeyBinding NEXT_SPELL = new KeyBinding("key." + Wizardry.MODID + ".next_spell", Keyboard.KEY_N, "key.categories." + Wizardry.MODID);
public static final KeyBinding PREVIOUS_SPELL = new KeyBinding("key." + Wizardry.MODID + ".previous_spell", Keyboard.KEY_B, "key.categories." + Wizardry.MODID);
@@ -161,6 +107,9 @@ public class ClientProxy extends CommonProxy {
// Armour Model
public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f);
/** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style)}). */
private static final int TOOLTIP_WRAP_WIDTH = 140;
// SECTION Registry
// ===============================================================================================================
@@ -175,16 +124,28 @@ public class ClientProxy extends CommonProxy {
ClientRegistry.registerKeyBinding(PREVIOUS_SPELL);
}
@Override
public void registerSpellHUD(){
MinecraftForge.EVENT_BUS.register(new GuiSpellDisplay(Minecraft.getMinecraft()));
}
@Override
public void initGuiBits(){
mixedFontRenderer = new MixedFontRenderer(Minecraft.getMinecraft().gameSettings, new ResourceLocation("textures/font/ascii.png"),
Minecraft.getMinecraft().renderEngine, false);
GuiWizardHandbook.initDisplayRecipes();
}
@Override
public void registerResourceReloadListeners(){
IResourceManager manager = Minecraft.getMinecraft().getResourceManager();
if(manager instanceof IReloadableResourceManager){
((IReloadableResourceManager)manager).registerReloadListener(GuiSpellDisplay::loadSkins);
((IReloadableResourceManager)manager).registerReloadListener(GuiWizardHandbook::loadHandbookFile);
}
}
// @Override
// public void registerSoundEventListener(){
// Minecraft.getMinecraft().getSoundHandler().addListener(ContinuousSpellSoundEntity::soundPlayed);
// }
public void registerAtlasMarkers(){
WizardryAntiqueAtlasIntegration.registerMarkers();
}
// SECTION Misc
@@ -194,6 +155,16 @@ public class ClientProxy extends CommonProxy {
public void setToNumberSliderEntry(Property property){
property.setConfigEntryClass(NumberSliderEntry.class);
}
@Override
public void setToHUDChooserEntry(Property property){
property.setConfigEntryClass(SpellHUDSkinChooserEntry.class);
}
@Override
public void setToNamedBooleanEntry(Property property){
property.setConfigEntryClass(NamedBooleanEntry.class);
}
@Override
public World getTheWorld(){
@@ -201,13 +172,66 @@ public class ClientProxy extends CommonProxy {
}
@Override
public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){
Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity(entity, sound, volume, pitch, repeat));
public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity<>(entity, sound, category, volume, pitch, repeat));
}
@Override
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellEntity(start, loop, end, spell, entity, volume, pitch));
}
@Override
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){
if(duration == -1){
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellDispenser(start, loop, end, spell, world, x, y, z, volume, pitch));
}else{
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellPosTimed(start, loop, end, spell, duration, x, y, z, volume, pitch));
}
}
@Override
public Set<String> getSpellHUDSkins(){
return GuiSpellDisplay.getSkinKeys();
}
// SECTION Items
// ===============================================================================================================
@Override
public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
EntityPlayerSP player = Minecraft.getMinecraft().player;
if(player == null) return false;
// Displayed recipe
if(Minecraft.getMinecraft().currentScreen instanceof GuiMerchant){
// It doesn't actually matter if the recipe is selected or not, since the itemstack will only ever
// match one of them anyway - and we'd have to reflect into GuiMerchant to get the selected recipe
MerchantRecipeList recipes = ((GuiMerchant)Minecraft.getMinecraft().currentScreen).getMerchant().getRecipes(player);
if(recipes != null && recipes.stream().anyMatch(r -> r.getItemToSell() == stack)){
// Spell books are always discovered when wizards are selling them
return true;
}
}
// Recipe output slot
// Required or players would be able to find out what the spell is without actually completing the trade
if(player.openContainer instanceof ContainerMerchant){
if(((ContainerMerchant)player.openContainer).getMerchantInventory().getStackInSlot(2) == stack){
return true;
}
}
if(!Wizardry.settings.discoveryMode) return true;
if(player.isCreative()) return true;
if(WizardData.get(player) != null && WizardData.get(player).hasSpellBeenDiscovered(spell)) return true;
return false;
}
@Override
public FontRenderer getFontRenderer(ItemStack stack){
@@ -216,12 +240,10 @@ public class ClientProxy extends CommonProxy {
if(stack.getItem() instanceof ItemWand){
spell = WandHelper.getCurrentSpell(stack);
}else if(stack.getItem() instanceof ItemSpellBook || stack.getItem() instanceof ItemScroll){
spell = Spell.get(stack.getItemDamage());
spell = Spell.byMetadata(stack.getItemDamage());
}
if(Minecraft.getMinecraft().player != null && Wizardry.settings.discoveryMode && WizardData.get(Minecraft.getMinecraft().player) != null
&& !Minecraft.getMinecraft().player.capabilities.isCreativeMode
&& !WizardData.get(Minecraft.getMinecraft().player).hasSpellBeenDiscovered(spell)){
if(!shouldDisplayDiscovered(spell, stack)){
return mixedFontRenderer;
}
@@ -231,16 +253,14 @@ public class ClientProxy extends CommonProxy {
@Override
public String getScrollDisplayName(ItemStack scroll){
// Displays [Empty slot] if spell is continuous.
Spell spell = Spell.get(scroll.getItemDamage());
if(spell.isContinuous) spell = Spells.none;
Spell spell = Spell.byMetadata(scroll.getItemDamage());
EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true;
// It seems that this method is called when the world is loading, before thePlayer has been initialised.
// If the player is null, the spell is assumed to be discovered.
if(player != null && Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null
if(player != null && Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
@@ -261,71 +281,59 @@ public class ClientProxy extends CommonProxy {
return super.getConjuredBowDurability(stack);
}
@Override
public void addMultiLineDescription(List<String> tooltip, String key, Style style){
String description = style.getFormattingCode() + I18n.format(key);
tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH));
}
// SECTION Particles
// ===============================================================================================================
/** Use {@link ParticleWizardry#registerParticle(ResourceLocation, IWizardryParticleFactory)}, this is internal. */
// I mean, it does exactly the same thing but I might want to make it do something else in future...
public static void addParticleFactory(ResourceLocation name, IWizardryParticleFactory factory){
factories.put(name, factory);
}
@Override
public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX, double velY, double velZ, int maxAge,
float r, float g, float b, boolean doGravity, double radius){
// Colour values are now automatically clamped to between 0 and 1, as values outside this range seem to
// cause strange effects in 1.10 (or more specifically, particles that are bright pink!)
// TODO: This is a terrible dirty fix, but it'll do for now. Find a nicer way in future.
if(type != WizardryParticleType.MAGIC_FIRE) r = MathHelper.clamp(r, 0, 1);
g = MathHelper.clamp(g, 0, 1);
b = MathHelper.clamp(b, 0, 1);
switch(type){
case BLIZZARD:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleBlizzard(world, maxAge, x, z, radius, y));
break;
case BRIGHT_DUST:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDust(world, x, y, z, velX, velY, velZ, r, g, b, false));
break;
case DARK_MAGIC:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDarkMagic(world, x, y, z, velX, velY, velZ, r, g, b));
break;
case DUST:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDust(world, x, y, z, velX, velY, velZ, r, g, b, true));
break;
case ICE:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleIce(world, x, y, z, velX, velY, velZ, maxAge));
break;
case LEAF:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleLeaf(world, x, y, z, velX, velY, velZ, maxAge));
break;
case MAGIC_BUBBLE:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleGiantBubble(world, x, y, z, velX, velY, velZ));
break;
case MAGIC_FIRE:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleMagicFlame(world, x, y, z, velX, velY, velZ, maxAge, r == 0 ? 1 + world.rand.nextFloat() : r));
break;
case PATH:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticlePath(world, x, y, z, velX, velY, velZ, r, g, b, maxAge));
break;
case SNOW:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleSnow(world, x, y, z, velX, velY, velZ));
break;
case SPARK:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleSpark(world, x, y, z, velX, velY, velZ));
break;
case SPARKLE:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleSparkle(world, x, y, z, velX, velY, velZ, r, g, b, maxAge, doGravity));
break;
case SPARKLE_ROTATING:
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleRotatingSparkle(world, maxAge, x, z, radius, y, r, g, b));
break;
default:
break;
public void registerParticles(){
// I'll be a good programmer and use the API method rather than the one above. Lead by example, as they say...
ParticleWizardry.registerParticle(Type.BEAM, ParticleBeam::new);
ParticleWizardry.registerParticle(Type.BUFF, ParticleBuff::new);
ParticleWizardry.registerParticle(Type.DARK_MAGIC, ParticleDarkMagic::new);
ParticleWizardry.registerParticle(Type.DUST, ParticleDust::new);
ParticleWizardry.registerParticle(Type.FLASH, ParticleFlash::new);
ParticleWizardry.registerParticle(Type.ICE, ParticleIce::new);
ParticleWizardry.registerParticle(Type.LEAF, ParticleLeaf::new);
ParticleWizardry.registerParticle(Type.LIGHTNING, ParticleLightning::new);
ParticleWizardry.registerParticle(Type.LIGHTNING_PULSE, ParticleLightningPulse::new);
ParticleWizardry.registerParticle(Type.MAGIC_BUBBLE, ParticleMagicBubble::new);
ParticleWizardry.registerParticle(Type.MAGIC_FIRE, ParticleMagicFlame::new);
ParticleWizardry.registerParticle(Type.PATH, ParticlePath::new);
ParticleWizardry.registerParticle(Type.SCORCH, ParticleScorch::new);
ParticleWizardry.registerParticle(Type.SNOW, ParticleSnow::new);
ParticleWizardry.registerParticle(Type.SPARK, ParticleSpark::new);
ParticleWizardry.registerParticle(Type.SPARKLE, ParticleSparkle::new);
ParticleWizardry.registerParticle(Type.SPHERE, ParticleSphere::new);
ParticleWizardry.registerParticle(Type.SUMMON, ParticleSummon::new);
ParticleWizardry.registerParticle(Type.VINE, ParticleVine::new);
}
@Override
public ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){
IWizardryParticleFactory factory = factories.get(type);
if(factory == null){
Wizardry.logger.warn("Unrecognised particle type {} ! Ensure the particle is properly registered.", type);
return null;
}
return factory.createParticle(world, x, y, z);
}
@Override
public void spawnTornadoParticle(World world, double x, double y, double z, double velX, double velZ, double radius, int maxAge,
IBlockState block, BlockPos pos){
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleTornado(world, maxAge, x, z, radius, y, velX, velZ, block).setBlockPos(pos));// ,
// world.rand.nextInt(6)));
Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleTornado(world, maxAge, x, z, radius, y, velX, velZ, block).setBlockPos(pos));// , world.rand.nextInt(6)));
}
// SECTION Packet Handlers
@@ -336,14 +344,12 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
Spell spell = Spell.get(message.spellID);
Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true
if(caster instanceof EntityPlayer){
((EntityPlayer)caster).setActiveHand(message.hand);
// Duration isn't needed because it only ever affects things server-side, and anything that is
// seen client-side gets synced elsewhere.
spell.cast(world, (EntityPlayer)caster, message.hand, 0, message.modifiers);
Source source = Source.OTHER;
@@ -358,19 +364,34 @@ public class ClientProxy extends CommonProxy {
// No need to check if the spell succeeded, because the packet is only ever sent when it succeeds.
// The handler for this event now deals with discovery.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityPlayer)caster, spell, message.modifiers, source));
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(source, spell, (EntityPlayer)caster, message.modifiers));
}else{
Wizardry.logger.warn("Recieved a PacketCastSpell, but the caster ID was not the ID of a player");
}
}
@Override
public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){
World world = Minecraft.getMinecraft().world;
Spell spell = Spell.byNetworkID(message.spellID);
spell.cast(world, message.position.x, message.position.y, message.position.z, message.direction, 0, message.duration, message.modifiers);
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.modifiers));
if(spell.isContinuous){
SpellEmitter.add(spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.duration, message.modifiers);
}
}
@Override
public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
Spell spell = Spell.get(message.spellID);
Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true
if(caster instanceof EntityPlayer){
@@ -380,7 +401,7 @@ public class ClientProxy extends CommonProxy {
if(data.isCasting()){
WizardData.get((EntityPlayer)caster).stopCastingContinuousSpell();
}else{
WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers);
WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers, message.duration);
}
}
}else{
@@ -394,7 +415,7 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
Entity target = message.targetID == -1 ? null : world.getEntityByID(message.targetID);
Spell spell = Spell.get(message.spellID);
Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true
if(caster instanceof EntityLiving){
@@ -403,7 +424,7 @@ public class ClientProxy extends CommonProxy {
spell.cast(world, (EntityLiving)caster, message.hand, 0, (EntityLivingBase)target, message.modifiers);
// Again, no need to check if the spell succeeded, because the packet is only ever sent when it
// succeeds.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityLiving)caster, spell, message.modifiers, Source.NPC));
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, (EntityLiving)caster, message.modifiers));
}
if(caster instanceof ISpellCaster){
@@ -412,10 +433,41 @@ public class ClientProxy extends CommonProxy {
((EntityLiving)caster).setAttackTarget((EntityLivingBase)target);
}
}
}else{
}else if(caster != null){
Wizardry.logger.warn("Recieved a PacketNPCCastSpell, but the caster ID was not the ID of an EntityLiving");
}
}
@Override
public void handleDispenserCastSpellPacket(PacketDispenserCastSpell.Message message){
World world = Minecraft.getMinecraft().world;
if(world.getTileEntity(message.pos) instanceof TileEntityDispenser){ // Should always be true
Spell spell = Spell.byNetworkID(message.spellID);
spell.cast(world, message.x, message.y, message.z, message.direction, 0, -1, message.modifiers);
// No need to check if the spell succeeded, because the packet is only ever sent when it succeeds.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.DISPENSER, spell, world, message.x, message.y,
message.z, message.direction, message.modifiers));
if(spell.isContinuous || spell instanceof None){
DispenserCastingData data = DispenserCastingData.get((TileEntityDispenser)world.getTileEntity(message.pos));
if(spell.isContinuous){
data.startCasting(spell, message.x, message.y, message.z, message.duration, message.modifiers);
}else{
data.stopCasting();
}
}
}else{
Wizardry.logger.warn("Recieved a PacketDispenserCastSpell, but no tileEntity was found at the supplied location.");
}
}
@Override
public void handleTransportationPacket(PacketTransportation.Message message){
@@ -423,78 +475,150 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
// Moved from when the packet is sent to when it is received; fixes the sound not playing in first person.
caster.playSound(SoundEvents.BLOCK_PORTAL_TRAVEL, 1, 1);
caster.playSound(WizardrySounds.SPELL_TRANSPORTATION_TRAVEL, 1, 1);
for(int i = 0; i < 20; i++){
double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle);
Minecraft.getMinecraft().effectRenderer
.addEffect(new ParticleSparkle(world, x, y, z, 0, 0.02, 0, 0.6f, 1.0f, 0.6f, 80 + world.rand.nextInt(10)));
double z = caster.posZ + radius * MathHelper.sin(angle);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.02, 0).clr(0.6f, 1, 0.6f)
.time(80 + world.rand.nextInt(10)).spawn(world);
}
for(int i = 0; i < 20; i++){
double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, x, y, z, 0, 0.02, 0);
}
for(int i = 0; i < 20; i++){
double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
world.spawnParticle(EnumParticleTypes.ENCHANTMENT_TABLE, x, y, z, 0, 0.02, 0);
}
}
@Override
public void handlePlayerSyncPacket(Message message){
public void handlePlayerSyncPacket(PacketPlayerSync.Message message){
WizardData properties = WizardData.get(Minecraft.getMinecraft().player);
WizardData data = WizardData.get(Minecraft.getMinecraft().player);
if(properties != null){
if(data != null){
properties.spellsDiscovered = message.spellsDiscovered;
data.synchronisedRandom.setSeed(message.seed);
data.spellsDiscovered = message.spellsDiscovered;
message.spellData.forEach(data::setVariable);
if(message.selectedMinionID == -1){
properties.selectedMinion = null;
data.selectedMinion = null;
}else{
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.selectedMinionID);
if(entity instanceof ISummonedCreature){
properties.selectedMinion = new WeakReference<ISummonedCreature>((ISummonedCreature)entity);
data.selectedMinion = new WeakReference<>((ISummonedCreature)entity);
}else{
properties.selectedMinion = null;
data.selectedMinion = null;
}
}
}
}
@Override
public void handleGlyphDataPacket(electroblob.wizardry.packet.PacketGlyphData.Message message){
public void handleGlyphDataPacket(PacketGlyphData.Message message){
SpellGlyphData data = SpellGlyphData.get(Minecraft.getMinecraft().world);
data.randomNames = new HashMap<Spell, String>();
data.randomDescriptions = new HashMap<Spell, String>();
data.randomNames = new HashMap<>();
data.randomDescriptions = new HashMap<>();
for(Spell spell : Spell.getSpells(Spell.allSpells)){
// -1 because the none spell isn't included
data.randomNames.put(spell, message.names.get(spell.id() - 1));
data.randomDescriptions.put(spell, message.descriptions.get(spell.id() - 1));
// This is a case where we must use the network ID, not the metadata
data.randomNames.put(spell, message.names.get(spell.networkID() - 1));
data.randomDescriptions.put(spell, message.descriptions.get(spell.networkID() - 1));
}
}
@Override
public void handleClairvoyancePacket(electroblob.wizardry.packet.PacketClairvoyance.Message message){
public void handleEmitterDataPacket(PacketEmitterData.Message message){
message.emitters.forEach(e -> e.setWorld(Minecraft.getMinecraft().world)); // Do this as soon as possible!
SpellEmitterData data = SpellEmitterData.get(Minecraft.getMinecraft().world);
// We shouldn't need to clear the emitters because when a player logs in or changes dimension the client world
// is wiped anyway, so the call to get() above should result in a fresh SpellEmitterData instance
message.emitters.forEach(data::add);
}
@Override
public void handleClairvoyancePacket(PacketClairvoyance.Message message){
Clairvoyance.spawnPathPaticles(Minecraft.getMinecraft().world, message.path, message.durationMultiplier);
}
@Override
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){
GuiWizardHandbook.updateUnlockStatus(message.showToasts, message.completedAdvancements);
}
@Override
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.hostID);
if(entity instanceof EntityLivingBase) PotionSlowTime.unblockNearbyEntities((EntityLivingBase)entity);
else Wizardry.logger.warn("Received a PacketEndSlowTime, but the entity ID did not match any living entity");
}
@Override
public void handleResurrectionPacket(PacketResurrection.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
if(entity instanceof EntityPlayer){
((Resurrection)Spells.resurrection).resurrect((EntityPlayer)entity);
if(entity == Minecraft.getMinecraft().player){
Minecraft.getMinecraft().world.spawnEntity(entity);
Minecraft.getMinecraft().displayGuiScreen(null);
}
}
else Wizardry.logger.warn("Received a PacketResurrection, but the entity ID did not match any player");
}
@Override
public void handlePossessionPacket(PacketPossession.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
if(entity instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)entity;
if(message.targetID == -1){
((Possession)Spells.possession).endPossession(player);
}else{
Entity target = Minecraft.getMinecraft().world.getEntityByID(message.targetID);
if(target instanceof EntityLiving){
((Possession)Spells.possession).possess(player, (EntityLiving)target, message.duration);
player.sendStatusMessage(new TextComponentTranslation("spell." + Spells.possession.getRegistryName()
+ ".success", Minecraft.getMinecraft().gameSettings.keyBindSneak.getDisplayName()), true);
}
else Wizardry.logger.warn("Received a PacketPossession, but the target ID did not match any living entity");
}
}
else Wizardry.logger.warn("Received a PacketPossession, but the player ID did not match any player");
}
public void handleConquerShrinePacket(PacketConquerShrine.Message message){
TileEntity tileEntity = Minecraft.getMinecraft().world.getTileEntity(new BlockPos(message.x, message.y, message.z));
if(tileEntity instanceof TileEntityShrineCore){
((TileEntityShrineCore)tileEntity).conquer();
}else Wizardry.logger.warn("Received a PacketConquerShrine, but there was no shrine core at the position sent");
}
// SECTION Rendering
// ===============================================================================================================
@@ -507,6 +631,7 @@ public class ClientProxy extends CommonProxy {
@Override
public void initialiseLayers(){
LayerStone.initialiseLayers();
LayerFrost.initialiseLayers();
}
@Override
@@ -516,6 +641,12 @@ public class ClientProxy extends CommonProxy {
// Yet another advantage to the new system: turns out you don't even need to register the renderer if you
// just want the vanilla one for the mob you're extending.
// Luckily for us, the vanilla husk renderer is only parametrised to EntityZombie
RenderingRegistry.registerEntityRenderingHandler(EntityHuskMinion.class, RenderHusk::new);
// This now extends AbstractSkeleton so we need to bind the renderer ourselves
RenderingRegistry.registerEntityRenderingHandler(EntitySkeletonMinion.class, RenderSkeleton::new);
RenderingRegistry.registerEntityRenderingHandler(EntityStrayMinion.class, RenderStrayMinion::new);
// An anonymous class in a lambda expression! No point writing a separate class really, is there?
RenderingRegistry.registerEntityRenderingHandler(EntityLightningWraith.class, manager -> new RenderBlaze(manager){
@Override
@@ -549,15 +680,15 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityForceArrow.class, RenderForceArrow::new);
// Creatures
RenderingRegistry.registerEntityRenderingHandler(EntitySpiritWolf.class, manager -> new RenderSpiritWolf(manager));
RenderingRegistry.registerEntityRenderingHandler(EntitySpiritHorse.class, manager -> new RenderSpiritHorse(manager));
RenderingRegistry.registerEntityRenderingHandler(EntitySpiritWolf.class, RenderSpiritWolf::new);
RenderingRegistry.registerEntityRenderingHandler(EntitySpiritHorse.class, RenderSpiritHorse::new);
RenderingRegistry.registerEntityRenderingHandler(EntityWizard.class, RenderWizard::new);
RenderingRegistry.registerEntityRenderingHandler(EntityEvilWizard.class, RenderEvilWizard::new);
RenderingRegistry.registerEntityRenderingHandler(EntityDecoy.class, RenderDecoy::new);
// Throwables
RenderingRegistry.registerEntityRenderingHandler(EntitySparkBomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/entity/spark_bomb.png"), false));
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/spark_bomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityFirebomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/firebomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityPoisonBomb.class,
@@ -576,21 +707,29 @@ public class ClientProxy extends CommonProxy {
manager -> new RenderLightningDisc(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 2.0f));
RenderingRegistry.registerEntityRenderingHandler(EntitySmokeBomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/smoke_bomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityEmber.class,
manager -> new RenderProjectile(manager, 0.15f, new ResourceLocation(Wizardry.MODID, "textures/entity/ember.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityMagicFireball.class,
manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityLargeMagicFireball.class,
manager -> new RenderProjectile(manager, 1.5f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityIceball.class,
manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/iceball.png"), false));
// Effects and constructs
RenderingRegistry.registerEntityRenderingHandler(EntityArc.class, RenderArc::new);
RenderingRegistry.registerEntityRenderingHandler(EntityBlackHole.class, RenderBlackHole::new);
RenderingRegistry.registerEntityRenderingHandler(EntityShield.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityBubble.class, RenderBubble::new);
RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new);
RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new);
RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderForcefield::new);
//RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new);
// Stuff that doesn't render
RenderingRegistry.registerEntityRenderingHandler(EntityBlizzard.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityTornado.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityArrowRain.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityShadowWraith.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityThunderbolt.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityStormElemental.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityEarthquake.class, RenderBlank::new);
@@ -608,7 +747,8 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityFireRing.class,
manager -> new RenderFireRing(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/ring_of_fire.png"), 5.0f));
RenderingRegistry.registerEntityRenderingHandler(EntityDecay.class, RenderDecay::new);
RenderingRegistry.registerEntityRenderingHandler(EntityLightningPulse.class, manager -> new RenderLightningPulse(manager, 8.0f));
RenderingRegistry.registerEntityRenderingHandler(EntityCombustionRune.class,
manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/combustion_rune.png"), 2.0f, true));
// TESRs
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArcaneWorkbench.class, new RenderArcaneWorkbench());
@@ -0,0 +1,247 @@
package electroblob.wizardry.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
/**
* Utility class containing some useful static methods for drawing GUIs. Previously these were spread across the main
* {@code WizardryUtilities} class and various individual GUI classes.
*
* @author Electroblob
* @since Wizardry 4.2
* @see MixedFontRenderer
*/
//@SideOnly(Side.CLIENT)
public final class DrawingUtils {
/**
* The integer colour for black passed into the font renderer methods. This used to be 0 but that's now white for
* some reason, so I've made a it a constant in case it changes again.
*/
// I think this is actually ever-so-slightly lighter than pure black, but the difference is unnoticeable.
public static final int BLACK = 1;
/**
* Shorthand for {@link DrawingUtils#drawTexturedRect(int, int, int, int, int, int, int, int)} which draws the
* entire texture (u and v are set to 0 and textureWidth and textureHeight are the same as width and height).
*/
public static void drawTexturedRect(int x, int y, int width, int height){
drawTexturedRect(x, y, 0, 0, width, height, width, height);
}
/**
* Draws a textured rectangle, taking the size of the image and the bit needed into
* account, unlike {@link net.minecraft.client.gui.Gui#drawTexturedModalRect(int, int, int, int, int, int)
* Gui.drawTexturedModalRect(int, int, int, int, int, int)}, which is harcoded for only 256x256 textures. Also handy
* for custom potion icons.
*
* @param x The x position of the rectangle
* @param y The y position of the rectangle
* @param u The x position of the top left corner of the section of the image wanted
* @param v The y position of the top left corner of the section of the image wanted
* @param width The width of the section
* @param height The height of the section
* @param textureWidth The width of the actual image.
* @param textureHeight The height of the actual image.
*/
public static void drawTexturedRect(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight){
DrawingUtils.drawTexturedFlippedRect(x, y, u, v, width, height, textureWidth, textureHeight, false, false);
}
/**
* Draws a textured rectangle, taking the size of the image and the bit needed into
* account, unlike {@link net.minecraft.client.gui.Gui#drawTexturedModalRect(int, int, int, int, int, int)
* Gui.drawTexturedModalRect(int, int, int, int, int, int)}, which is harcoded for only 256x256 textures. Also handy
* for custom potion icons. This version allows the texture to additionally be flipped in x and/or y.
*
* @param x The x position of the rectangle
* @param y The y position of the rectangle
* @param u The x position of the top left corner of the section of the image wanted
* @param v The y position of the top left corner of the section of the image wanted
* @param width The width of the section
* @param height The height of the section
* @param textureWidth The width of the actual image.
* @param textureHeight The height of the actual image.
* @param flipX Whether to flip the texture in the x direction.
* @param flipY Whether to flip the texture in the y direction.
*/
public static void drawTexturedFlippedRect(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, boolean flipX, boolean flipY){
float f = 1F / (float)textureWidth;
float f1 = 1F / (float)textureHeight;
int u1 = flipX ? u + width : u;
int u2 = flipX ? u : u + width;
int v1 = flipY ? v + height : v;
int v2 = flipY ? v : v + height;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(org.lwjgl.opengl.GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX);
buffer.pos((double)(x), (double)(y + height), 0).tex((double)((float)(u1) * f), (double)((float)(v2) * f1)).endVertex();
buffer.pos((double)(x + width), (double)(y + height), 0).tex((double)((float)(u2) * f), (double)((float)(v2) * f1)).endVertex();
buffer.pos((double)(x + width), (double)(y), 0).tex((double)((float)(u2) * f), (double)((float)(v1) * f1)).endVertex();
buffer.pos((double)(x), (double)(y), 0).tex((double)((float)(u1) * f), (double)((float)(v1) * f1)).endVertex();
tessellator.draw();
}
/**
* Draws a textured rectangle, stretching the section of the image to fit the size given.
*
* @param x The x position of the rectangle
* @param y The y position of the rectangle
* @param u The x position of the top left corner of the section of the image wanted, expressed as a fraction of the
* image width
* @param v The y position of the top left corner of the section of the image wanted, expressed as a fraction of the
* image width
* @param finalWidth The width as rendered
* @param finalHeight The height as rendered
* @param width The width of the section, expressed as a fraction of the image width
* @param height The height of the section, expressed as a fraction of the image width
*/
public static void drawTexturedStretchedRect(int x, int y, int u, int v, int finalWidth, int finalHeight, int width,
int height){
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos((x), y + finalHeight, 0).tex(u, v + height).endVertex();
buffer.pos(x + finalWidth, y + finalHeight, 0).tex(u + width, v + height).endVertex();
buffer.pos(x + finalWidth, (y), 0).tex(u + width, v).endVertex();
buffer.pos((x), (y), 0).tex(u, v).endVertex();
tessellator.draw();
}
/**
* Mixes the two given opaque colours in the proportion specified.
* @param colour1 The first colour to mix, as a 6-digit hexadecimal.
* @param colour2 The second colour to mix, as a 6-digit hexadecimal.
* @param proportion The proportion of the second colour; will be clamped to between 0 and 1.
* @return The resulting colour, as a 6-digit hexadecimal.
*/
public static int mix(int colour1, int colour2, float proportion){
proportion = MathHelper.clamp(proportion, 0, 1);
int r1 = colour1 >> 16 & 255;
int g1 = colour1 >> 8 & 255;
int b1 = colour1 & 255;
int r2 = colour2 >> 16 & 255;
int g2 = colour2 >> 8 & 255;
int b2 = colour2 & 255;
int r = (int)(r1 + (r2-r1) * proportion);
int g = (int)(g1 + (g2-g1) * proportion);
int b = (int)(b1 + (b2-b1) * proportion);
return (r << 16) + (g << 8) + b;
}
/**
* Makes the given opaque colour translucent with the given opacity.
* @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque).
* @param opacity The opacity to apply to the given colour, as a fraction between 0 and 1.
* @return The resulting integer colour code, which will be an 8-digit hexadecimal.
*/
public static int makeTranslucent(int colour, float opacity){
return colour + ((int)(0xff * opacity * 0x01000000));
}
/**
* Draws the given string at the given position, scaling it if it does not fit within the given width.
* @param font A {@code FontRenderer} object.
* @param text The text to display.
* @param x The x position of the top-left corner of the text.
* @param y The y position of the top-left corner of the text.
* @param scale The scale that the text should normally be if it does not exceed the maximum width.
* @param colour The colour to render the text in, supports translucency.
* @param width The maximum width of the text. <i>This is not scaled; you should pass in the width of the actual
* area of the screen in which the text needs to fit, regardless of the scale parameter.</i>
* @param centre Whether to adjust the y position such that the centre of the text lines up with where its centre
* would be if it was not scaled (automatically or manually).
* @param alignR True to right-align the text, false for normal left alignment.
*/
public static void drawScaledStringToWidth(FontRenderer font, String text, float x, float y, float scale, int colour, float width, boolean centre, boolean alignR){
float textWidth = font.getStringWidth(text) * scale;
float textHeight = font.FONT_HEIGHT * scale;
if(textWidth > width){
scale *= width/textWidth;
}else if(alignR){ // Alignment makes no difference if the string fills the entire width
x += width - textWidth;
}
if(centre) y += (font.FONT_HEIGHT - textHeight)/2;
DrawingUtils.drawScaledTranslucentString(font, text, x, y, scale, colour);
}
/** Draws the given string at the given position, scaling the text by the specified factor. Also enables blending to
* render text in semitransparent colours (e.g. 0x88ffffff). */
public static void drawScaledTranslucentString(FontRenderer font, String text, float x, float y, float scale, int colour){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.scale(scale, scale, scale);
// Because we scaled the entire rendering space, the coordinates have to be scaled inversely
x /= scale;
y /= scale;
font.drawStringWithShadow(text, x, y, colour);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
/**
* Draws an itemstack and (optionally) its tooltip, directly. Mainly intended for use outside of GUI classes, since
* most of the GL state changes done in this method (which are the main reason it exists at all) are already done
* when drawing a GUI.
*
* @param gui An instance of a GUI class.
* @param stack The itemstack to draw.
* @param x The x position of the left-hand edge of the itemstack.
* @param y The y position of the top edge of the itemstack.
* @param mouseX The x position of the mouse, used for tooltip positioning.
* @param mouseY The y position of the mouse, used for tooltip positioning.
* @param tooltip Whether to draw the tooltip.
*/
public static void drawItemAndTooltip(GuiContainer gui, ItemStack stack, int x, int y, int mouseX, int mouseY, boolean tooltip){
RenderItem renderItem = Minecraft.getMinecraft().getRenderItem();
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
GlStateManager.enableLighting();
renderItem.zLevel = 100.0F;
if(!stack.isEmpty()){
renderItem.renderItemAndEffectIntoGUI(stack, x, y);
renderItem.renderItemOverlays(Minecraft.getMinecraft().fontRenderer, stack, x, y);
if(tooltip){
gui.drawHoveringText(gui.getItemToolTip(stack), mouseX + gui.getXSize()/2 - gui.width/2,
mouseY + gui.getYSize()/2 - gui.height/2);
}
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
RenderHelper.enableStandardItemLighting();
}
}
@@ -1,253 +0,0 @@
package electroblob.wizardry.client;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
public class GuiArcaneWorkbench extends GuiContainer {
private GuiButton applyBtn;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID,
"textures/gui/arcane_workbench.png");
private IInventory playerInventory;
private IInventory arcaneWorkbenchInventory;
private final int tooltipWidth = 164;
// We report the actual size of the GUI to Minecraft when a wand is in so JEI doesn't overdraw it.
// For calculations, we use the size without the tooltip.
private final int xSizeNoTip = 176;
public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){
super(new ContainerArcaneWorkbench(invPlayer, entity));
this.playerInventory = invPlayer;
this.arcaneWorkbenchInventory = entity;
xSize = xSizeNoTip;
ySize = 220;
}
@Override
public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_){
// Tests if there is a wand in the workbench and edits the positioning accordingly
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack().getItem() instanceof ItemWand){
xSize = xSizeNoTip + tooltipWidth;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = (this.width - tooltipWidth) / 2 + 48;
}else{
xSize = xSizeNoTip;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = this.width / 2 + 48;
}
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack()){
this.applyBtn.enabled = true;
}else{
this.applyBtn.enabled = false;
}
super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_);
// Required now, or item mouseover tooltips won't render.
this.renderHoveredToolTip(p_73863_1_, p_73863_2_);
}
@Override
public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY){
GlStateManager.pushAttrib();
GlStateManager.color(1F, 1F, 1F, 1F);
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Main inventory
drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSizeNoTip, ySize);
// Changing slots
for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){
Slot slot = this.inventorySlots.getSlot(i);
if(slot.xPos >= 0 && slot.yPos >= 0)
this.drawTexturedModalRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 0, 220, 36, 36);
}
// Tooltip only drawn if there is a wand
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack().getItem() instanceof ItemWand){
// Tooltip box
drawTexturedModalRect(guiLeft + xSizeNoTip, guiTop, xSizeNoTip, 0, 256 - xSizeNoTip - 4, ySize);
drawTexturedModalRect(guiLeft + 252, guiTop, xSizeNoTip + 4, 0, tooltipWidth - 2 * (256 - xSizeNoTip - 4), ySize);
drawTexturedModalRect(guiLeft + xSize - (256 - xSizeNoTip - 4), guiTop, xSizeNoTip + 4, 0,
256 - xSizeNoTip - 4, ySize);
ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack();
Spell[] spells = WandHelper.getSpells(wand);
int i = 0;
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
// As of Wizardry 1.2, the icons have been split off into their own texture files to allow for add-on
// mods to add their own.
Minecraft.getMinecraft().renderEngine
.bindTexture(discovered ? spell.element.getIcon() : Element.MAGIC.getIcon());
// Renders the little element icon
WizardryUtilities.drawTexturedRect(guiLeft + xSizeNoTip + 5, guiTop + 34 + 10 * i++, 8, 8);
}
int x = 0;
int y = guiTop + 50 + spells.length * 10;
// Look how much shorter this is with the WandHelper class!
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(wand, item);
if(level > 0){
ItemStack stack = new ItemStack(item, level);
GlStateManager.enableDepth();
this.itemRender.renderItemAndEffectIntoGUI(stack, guiLeft + xSizeNoTip + 6 + x, y);
this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack, guiLeft + xSizeNoTip + 6 + x, y,
null);
x += 18;
GlStateManager.disableDepth();
}
}
}
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Fixes the bug that caused the slot hightlight to render opaque. I don't know why it works, it just works!
GlStateManager.disableBlend();
GlStateManager.enableAlpha();
GlStateManager.popAttrib();
}
@Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){
this.fontRenderer
.drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName()
: I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752);
this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName()
: I18n.format(this.playerInventory.getName()), 8, this.ySize - 96 + 2, 4210752);
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack().getItem() instanceof ItemWand){
ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack();
this.fontRenderer.drawStringWithShadow("\u00A7f" + wand.getDisplayName(), xSizeNoTip + 6, 6, 0);
this.fontRenderer.drawStringWithShadow(
"\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana") + " "
+ (wand.getMaxDamage() - wand.getItemDamage()) + "/" + wand.getMaxDamage(),
xSizeNoTip + 6, 20, 0);
Spell[] spells = WandHelper.getSpells(wand);
int y = 34;
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
if(discovered){
this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), xSizeNoTip + 16, y, 0);
}else{
this.mc.standardGalacticFontRenderer.drawStringWithShadow(
"\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), xSizeNoTip + 16, y, 0);
}
y += 10;
}
if(WandHelper.getTotalUpgrades(wand) > 0){
this.fontRenderer.drawStringWithShadow(
"\u00A7f" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.upgrades"), xSizeNoTip + 6, y + 6, 0);
int x = 0;
y = 50 + spells.length * 10;
// Wand upgrade tooltips
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(wand, item);
if(level > 0){
// The javadoc for isPointInRegion is ambiguous; what it means is that the REGION is
// relative to the GUI but the POINT isn't.
if(isPointInRegion(xSizeNoTip + 6 + x, y, 16, 16, mouseX, mouseY)){
ItemStack stack = new ItemStack(item, level);
this.renderToolTip(stack, mouseX - guiLeft, mouseY - guiTop);
}
x += 18;
}
}
}
}
}
@Override
public void initGui(){
this.mc.player.openContainer = this.inventorySlots;
this.guiLeft = (this.width - this.xSize) / 2;
this.guiTop = (this.height - this.ySize) / 2;
Keyboard.enableRepeatEvents(true);
this.buttonList.clear();
this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 48, this.height / 2 + 3));
}
@Override
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
@Override
protected void actionPerformed(GuiButton button){
if(button.enabled){
if(button.id == 0){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON);
WizardryPacketHandler.net.sendToServer(msg);
}
}
}
}
@@ -1,42 +0,0 @@
package electroblob.wizardry.client;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
class GuiButtonApply extends GuiButton {
public GuiButtonApply(int id, int x, int y){
super(id, x, y, 32, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply"));
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
// Whether the button is highlighted
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
int k = 36;
int l = 220;
int colour = 14737632;
if(this.enabled){
if(this.hovered){
k += this.width * 2;
colour = 16777120;
}
}else{
k += this.width;
colour = 10526880;
}
WizardryUtilities.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 256, 256);
this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2,
this.y + (this.height - 8) / 2, colour);
}
}
@@ -1,47 +0,0 @@
package electroblob.wizardry.client;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
class GuiButtonTurnPage extends GuiButton {
/** True for pointing right (next page), false for pointing left (previous page). */
private final boolean nextPage;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png");
public GuiButtonTurnPage(int id, int x, int y, boolean isNextPage){
super(id, x, y, 23, 13, "");
this.nextPage = isNextPage;
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
if(this.visible){
boolean flag = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
minecraft.getTextureManager().bindTexture(texture);
int k = 0;
int l = 192;
if(flag){
k += 23;
}
if(!this.nextPage){
l += 13;
}
WizardryUtilities.drawTexturedRect(this.x, this.y, k, l, 23, 13, 288, 256);
}
}
}
@@ -1,80 +0,0 @@
package electroblob.wizardry.client;
import java.util.ArrayList;
import java.util.List;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.common.config.ConfigElement;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyCategoryElement;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry;
import net.minecraftforge.fml.client.config.IConfigElement;
public class GuiConfigWizardry extends GuiConfig {
public GuiConfigWizardry(GuiScreen parent){
super(parent, getConfigEntries(), Wizardry.MODID, false, false,
Wizardry.NAME + " - " + I18n.format("config." + Wizardry.MODID + ".title.general"));
// this.titleLine2 = "File location: " + Wizardry.config.getConfigFile().getAbsolutePath();
}
private static List<IConfigElement> getConfigEntries(){
List<IConfigElement> configList = new ArrayList<IConfigElement>(1);
configList.add(new DummyCategoryElement("spellsConfig", "config." + Wizardry.MODID + ".category." + Settings.SPELLS_CATEGORY,
SpellsCategory.class));
configList.add(new DummyCategoryElement("resistancesConfig",
"config.wizardry.category." + Settings.RESISTANCES_CATEGORY, ResistancesCategory.class));
configList.addAll(new ConfigElement(Wizardry.settings.getConfigCategory(Configuration.CATEGORY_GENERAL))
.getChildElements());
return configList;
}
/** Spells category of the config gui. This adds a button which opens up the spells category config. */
public static class SpellsCategory extends CategoryEntry {
public SpellsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override
protected GuiScreen buildChildScreen(){
// This GuiConfig object specifies the configID of the object and as such will force-save when it is closed.
// The parent GuiConfig object's entryList will also be refreshed to reflect the changes.
GuiConfig spellsMenu = new GuiConfig(this.owningScreen,
(new ConfigElement(Wizardry.settings.getConfigCategory(Settings.SPELLS_CATEGORY)))
.getChildElements(),
this.owningScreen.modID, Settings.SPELLS_CATEGORY, false, false,
Wizardry.NAME + " - " + I18n.format("config." + Wizardry.MODID + ".title." + Settings.SPELLS_CATEGORY));
spellsMenu.titleLine2 = I18n.format("config." + Wizardry.MODID + ".subtitle." + Settings.SPELLS_CATEGORY);
return spellsMenu;
}
}
/** Resistances category of the config gui. */
public static class ResistancesCategory extends CategoryEntry {
public ResistancesCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override
protected GuiScreen buildChildScreen(){
// This GuiConfig object specifies the configID of the object and as such will force-save when it is closed.
// The parent GuiConfig object's entryList will also be refreshed to reflect the changes.
GuiConfig idsMenu = new GuiConfig(this.owningScreen,
(new ConfigElement(Wizardry.settings.getConfigCategory(Settings.RESISTANCES_CATEGORY)))
.getChildElements(),
this.owningScreen.modID, Settings.RESISTANCES_CATEGORY, false, false,
Wizardry.NAME + " - " + I18n.format("config." + Wizardry.MODID + ".title." + Settings.RESISTANCES_CATEGORY));
idsMenu.titleLine2 = I18n.format("config." + Wizardry.MODID + ".subtitle." + Settings.RESISTANCES_CATEGORY);
return idsMenu;
}
}
}
@@ -1,112 +0,0 @@
package electroblob.wizardry.client;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
public class GuiSpellBook extends GuiScreen {
private int xSize, ySize;
private Spell spell;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/spellbook.png");
public GuiSpellBook(Spell spell){
super();
xSize = 288;
ySize = 180;
this.spell = spell;
}
/**
* Draws the screen and all the components in it.
*/
public void drawScreen(int par1, int par2, float par3){
int xPos = this.width / 2 - xSize / 2;
int yPos = this.height / 2 - this.ySize / 2;
EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true;
if(Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
// Draws spell illustration on opposite page, underneath the book so it shows through the hole.
Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
WizardryUtilities.drawTexturedRect(xPos + 145, yPos + 20, 0, 0, 128, 128, 128, 128);
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
WizardryUtilities.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256);
super.drawScreen(par1, par2, par3);
if(discovered){
this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 14, 0);
this.fontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 25, 0x777777);
}else{
this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17,
yPos + 14, 0);
this.mc.standardGalacticFontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 25,
0x777777);
}
this.fontRenderer.drawString("-------------------", xPos + 17, yPos + 34, 0);
if(spell.tier == Tier.BASIC){
// Basic is usually white but this doesn't show up.
this.fontRenderer.drawString("Tier: \u00A77" + Tier.BASIC.getDisplayName(), xPos + 17, yPos + 44, 0);
}else{
this.fontRenderer.drawString("Tier: " + spell.tier.getDisplayNameWithFormatting(), xPos + 17, yPos + 44,
0);
}
String element = "Element: " + spell.element.getFormattingCode() + spell.element.getDisplayName();
if(!discovered) element = "Element: ?";
this.fontRenderer.drawString(element, xPos + 17, yPos + 56, 0);
String manaCost = "Mana Cost: " + spell.cost;
if(spell.isContinuous) manaCost = "Mana Cost: " + spell.cost + "/second";
if(!discovered) manaCost = "Mana Cost: ?";
this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 68, 0);
if(discovered){
this.fontRenderer.drawSplitString(spell.getDescription(), xPos + 17, yPos + 82, 118, 0);
}else{
this.mc.standardGalacticFontRenderer.drawSplitString(
SpellGlyphData.getGlyphDescription(spell, player.world), xPos + 17, yPos + 82, 118, 0);
}
/* // Word wrapping int charNumber = 0; int lineNumber = 0;
*
* while(charNumber < spell.desc.length()){ int lineLength = 0; String line; if(spell.desc.length() - charNumber
* > 22){ for(int i = charNumber; i < charNumber+23; i++){ if(spell.desc.charAt(i) == ' '){ lineLength = i -
* charNumber; } } line = spell.desc.substring(charNumber, charNumber + lineLength); }else{ line =
* spell.desc.substring(charNumber, spell.desc.length()); charNumber = spell.desc.length(); }
* this.fontRendererObj.drawString("\u00A7o" + line, xPos+17, yPos+82+10*lineNumber, 0);
* charNumber+=(lineLength+1); lineNumber++; } */
}
public void initGui(){
super.initGui();
Keyboard.enableRepeatEvents(true);
this.buttonList.clear();
}
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
}
@@ -1,154 +0,0 @@
package electroblob.wizardry.client;
import java.util.List;
import electroblob.wizardry.Settings.GuiPosition;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
public class GuiSpellDisplay extends Gui {
private Minecraft mc;
private static final ResourceLocation hudTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud.png");
public GuiSpellDisplay(Minecraft par1Minecraft){
super();
this.mc = par1Minecraft;
}
@SubscribeEvent
public void draw(RenderGameOverlayEvent event){
EntityPlayer player = this.mc.player;
// If the player has a wand in each hand, only displays for the one in the main hand.
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
int width = event.getResolution().getScaledWidth();
int height = event.getResolution().getScaledHeight();
Spell spell = WandHelper.getCurrentSpell(wand);
int cooldown = WandHelper.getCurrentCooldown(wand);
float cooldownMultiplier = 1.0f - WandHelper.getUpgradeLevel(wand, WizardryItems.cooldown_upgrade) * Constants.COOLDOWN_REDUCTION_PER_LEVEL;
if(player.isPotionActive(WizardryPotions.font_of_mana)){
// Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously
cooldownMultiplier /= 2 + player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier();
}
// Coordinates of the top left corner of the HUD.
int left = 0;
int top = 0;
boolean mirror = false;
if(Wizardry.settings.spellHUDPosition == GuiPosition.BOTTOM_LEFT){
left = 0;
top = height - 36;
}else if(Wizardry.settings.spellHUDPosition == GuiPosition.TOP_LEFT){
left = 0;
top = 0;
}else if(Wizardry.settings.spellHUDPosition == GuiPosition.TOP_RIGHT){
left = width - 128;
top = 0;
mirror = true;
}else if(Wizardry.settings.spellHUDPosition == GuiPosition.BOTTOM_RIGHT){
left = width - 128;
top = height - 36;
mirror = true;
}
boolean discovered = true;
if(!player.capabilities.isCreativeMode && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
}
if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){
// Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect
String colour = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.element.getFormattingCode();
if(!discovered) colour = "\u00A79";
String spellName = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world);
FontRenderer font = discovered ? this.mc.fontRenderer : this.mc.standardGalacticFontRenderer;
int maxWidth = 90;
if(font.getStringWidth(spellName) <= maxWidth){
// Single line is rendered more centrally
font.drawStringWithShadow(colour + spellName, mirror ? left + 5 : left + 41, top + 13, 0xffffffff);
}else{
int lineNumber = 0;
List<String> lines = font.listFormattedStringToWidth(spellName, maxWidth);
for(Object line : lines){
if(line instanceof String){
font.drawStringWithShadow(colour + (String)line, mirror ? left + 5 : left + 41, top + 6 + 11 * lineNumber, 0xffffffff);
}
lineNumber++;
}
}
}else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){
GlStateManager.pushAttrib();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1);
this.mc.renderEngine.bindTexture(hudTexture);
// Background of spell hud
this.drawTexturedModalRect(left, top, 0, mirror ? 36 : 0, 128, 36);
// Cooldown bar
if(cooldown > 0){
this.drawTexturedModalRect(mirror ? left + 5 : left + 41, height - 8, 128, 6, 82, 6);
int l = (int)(((double)(spell.cooldown * cooldownMultiplier - cooldown) / (double)(spell.cooldown * cooldownMultiplier)) * 82);
this.drawTexturedModalRect(mirror ? left + 5 : left + 41, height - 8, 128, 0, l, 6);
}
// Spell illustration
this.mc.renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
WizardryUtilities.drawTexturedRect(mirror ? left + 94 : left + 2, top + 2, 0, 0, 32, 32, 32, 32);
GlStateManager.popAttrib();
}
}
}
@@ -1,709 +0,0 @@
package electroblob.wizardry.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
public class GuiWizardHandbook extends GuiScreen {
private int xSize, ySize;
private int pageNumber = 0;
private static final int PAGE_WIDTH = 120;
/**
* The integer colour for black passed into the font renderer methods. This used to be 0 but that's now white for
* some reason, so I've made a it a constant in case it changes again.
*/
// I think this is actually ever-so-slightly lighter than pure black, but the difference is unnoticeable.
private static final int BLACK = 1;
public static final ResourceLocation regularHandbook = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png");
public static final ResourceLocation ore = new ResourceLocation(Wizardry.MODID, "textures/gui/ore_picture.png");
public static final ResourceLocation crystal = new ResourceLocation(Wizardry.MODID, "textures/items/magic_crystal.png");
public static final ResourceLocation workbenchGui = new ResourceLocation(Wizardry.MODID, "textures/gui/arcane_workbench.png");
public static final ResourceLocation craftingGrids = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook_recipes.png");
private List<ArrayList<String>> text;
private List<Section> sections;
private static final List<Pair<ItemStack, NonNullList<NonNullList<ItemStack>>>> RECIPES = new ArrayList<>();
private int guiPage, imagePage;
public GuiWizardHandbook(){
super();
xSize = 288;
ySize = 180;
}
@Override
public void drawScreen(int mouseX, int mouseY, float par3){
int xPos = this.width / 2 - xSize / 2;
int yPos = this.height / 2 - this.ySize / 2;
// Tests for crafting recipes section
if(pageNumber >= (sections.get(sections.size() - 1).pageNumber - 1) / 2
&& pageNumber < (sections.get(sections.size() - 1).pageNumber - 1) / 2 + 4){
Minecraft.getMinecraft().renderEngine.bindTexture(craftingGrids);
}else{
Minecraft.getMinecraft().renderEngine.bindTexture(regularHandbook);
}
WizardryUtilities.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256);
// Arcane workbench gui picture
if(pageNumber == (this.guiPage - 1) / 2){
Minecraft.getMinecraft().renderEngine.bindTexture(workbenchGui);
this.drawTexturedModalRect(this.guiPage % 2 == 1 ? xPos + 17 : this.width / 2 + 7, yPos + 14, 28, 12, 120,
118);
}
// Magic crystal and crystal ore images
if(pageNumber == (this.imagePage - 1) / 2){
Minecraft.getMinecraft().renderEngine.bindTexture(ore);
WizardryUtilities.drawTexturedRect(this.imagePage % 2 == 1 ? xPos + 17 : this.width / 2 + 7, yPos + 80, 0,
0, 64, 64, 64, 64);
Minecraft.getMinecraft().renderEngine.bindTexture(crystal);
drawTexturedStretchedRect(this.imagePage % 2 == 1 ? xPos + 17 + 64 : this.width / 2 + 7 + 62, yPos + 80, 0,
0, 64, 64, 1, 1);
}
this.fontRenderer.drawString("" + (pageNumber * 2 + 1), xPos + xSize / 4 - 3, yPos + ySize - 20, 0);
this.fontRenderer.drawString("" + (pageNumber * 2 + 2), xPos + 3 * xSize / 4 - 5, yPos + ySize - 20, 0);
super.drawScreen(mouseX, mouseY, par3);
int lineNumber = 0;
if(pageNumber == 1){
for(Section s : sections){
s.drawContents();
}
}else{
for(Section s : sections){
s.hideButton();
}
}
for(String paragraph : text.get(pageNumber * 2)){
this.fontRenderer.drawSplitString(paragraph, xPos + 17,
yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK);
List<String> list = new ArrayList<String>(
this.fontRenderer.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH));
lineNumber += list.size();
}
lineNumber = 0;
// Prevents crash when the last page is blank (and hence is not in the list of pages)
if(text.size() > pageNumber * 2 + 1){
for(String paragraph : text.get(pageNumber * 2 + 1)){
// First page is centred
if(pageNumber == 0){
int startX = this.width / 2 + 7 + PAGE_WIDTH / 2
- this.fontRenderer.getStringWidth(paragraph) / 2;
this.fontRenderer.drawSplitString(paragraph, startX,
yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK);
}else{
this.fontRenderer.drawSplitString(paragraph, this.width / 2 + 7,
yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK);
}
List<String> list = new ArrayList<String>(
this.fontRenderer.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH));
lineNumber += list.size();
}
}
// Which page of the recipes this is
int recipePage = pageNumber - (sections.get(sections.size() - 1).pageNumber - 1) / 2;
if(recipePage >= 0 && recipePage < 4){
// 4 recipes per page, hence the recipePage*4
this.renderCraftingRecipe(xPos + 23, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4).getRight(), RECIPES.get(recipePage*4).getLeft());
this.renderCraftingRecipe(xPos + 23, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+1).getRight(), RECIPES.get(recipePage*4+1).getLeft());
this.renderCraftingRecipe(xPos + 156, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4+2).getRight(), RECIPES.get(recipePage*4+2).getLeft());
this.renderCraftingRecipe(xPos + 156, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+3).getRight(), RECIPES.get(recipePage*4+3).getLeft());
// Tooltips are rendered after recipes to prevent tooltips on the left appearing behind items on the right.
this.renderCraftingTooltips(xPos + 23, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4).getRight(), RECIPES.get(recipePage*4).getLeft());
this.renderCraftingTooltips(xPos + 23, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+1).getRight(), RECIPES.get(recipePage*4+1).getLeft());
this.renderCraftingTooltips(xPos + 156, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4+2).getRight(), RECIPES.get(recipePage*4+2).getLeft());
this.renderCraftingTooltips(xPos + 156, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+3).getRight(), RECIPES.get(recipePage*4+3).getLeft());
}
}
// TODO: In 1.12, this all needs redoing nicely. With the crafting system halfway through changing in 1.11.2, this
// isn't worth doing until then.
private void renderCraftingRecipe(int xPos, int yPos, int mouseX, int mouseY, NonNullList<NonNullList<ItemStack>> craftingGrid,
ItemStack craftingResult){
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
GlStateManager.enableLighting();
itemRender.zLevel = 100.0F;
for(int i = 0; i < craftingGrid.size(); i++){
for(int j = 0; j < craftingGrid.get(i).size(); j++){
ItemStack stack = craftingGrid.get(i).get(j);
if(!stack.isEmpty()){
itemRender.renderItemAndEffectIntoGUI(stack, xPos + 18 * i, yPos + 18 * j);
itemRender.renderItemOverlays(this.fontRenderer, stack, xPos + 18 * i,
yPos + 18 * j);
}
}
}
if(!craftingResult.isEmpty()){
itemRender.renderItemAndEffectIntoGUI(craftingResult, xPos + 86, yPos + 18);
itemRender.renderItemOverlays(this.fontRenderer, craftingResult, xPos + 86, yPos + 18);
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GL11.glEnable(GL11.GL_DEPTH_TEST);
RenderHelper.enableStandardItemLighting();
}
private void renderCraftingTooltips(int xPos, int yPos, int mouseX, int mouseY, NonNullList<NonNullList<ItemStack>> craftingGrid,
ItemStack craftingResult){
int guiLeft = this.width / 2 - xSize / 2;
int guiTop = this.height / 2 - this.ySize / 2;
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
itemRender.zLevel = 0.0F;
GlStateManager.disableLighting();
for(int i = 0; i < craftingGrid.size(); i++){
for(int j = 0; j < craftingGrid.get(i).size(); j++){
ItemStack stack = craftingGrid.get(i).get(j);
if(!stack.isEmpty()
&& isPointInRegion(xPos + 18 * i, yPos + 18 * j, 16, 16, mouseX + guiLeft, mouseY + guiTop)){
this.renderToolTip(stack, mouseX, mouseY);
}
}
}
if(!craftingResult.isEmpty() && isPointInRegion(xPos + 86, yPos + 18, 16, 16, mouseX + guiLeft, mouseY + guiTop)){
this.renderToolTip(craftingResult, mouseX, mouseY);
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GL11.glEnable(GL11.GL_DEPTH_TEST);
RenderHelper.enableStandardItemLighting();
}
@Override
public void initGui(){
super.initGui();
Keyboard.enableRepeatEvents(true);
int nextButtonId = 0;
this.buttonList.clear();
this.buttonList.add(new GuiButtonTurnPage(nextButtonId++, this.width / 2 + this.xSize / 2 - 22 - 23,
this.height / 2 + this.ySize / 2 - 10 - 13, true));
this.buttonList.add(new GuiButtonTurnPage(nextButtonId++, this.width / 2 - this.xSize / 2 + 21,
this.height / 2 + this.ySize / 2 - 10 - 13, false));
text = new ArrayList<ArrayList<String>>(1);
sections = new ArrayList<Section>(1);
BufferedReader bufferedreader = null;
String textFilepath = Wizardry.MODID + ":texts/handbook_"
+ Minecraft.getMinecraft().getLanguageManager().getCurrentLanguage().getLanguageCode() + ".txt";
try{
bufferedreader = new BufferedReader(new InputStreamReader(
this.mc.getResourceManager().getResource(new ResourceLocation(textFilepath)).getInputStream(),
StandardCharsets.UTF_8));
}catch (IOException e){
Wizardry.logger.info(
"Wizard handbook text file missing for the current language. Using default (English - US) instead.");
textFilepath = Wizardry.MODID + ":texts/handbook_en_us.txt";
try {
bufferedreader = new BufferedReader(new InputStreamReader(
this.mc.getResourceManager().getResource(new ResourceLocation(textFilepath)).getInputStream(),
StandardCharsets.UTF_8));
} catch (IOException x){
Wizardry.logger.error("Couldn't find file: " + Wizardry.MODID + "/assets/texts/handbook_en_us.txt. The file may be"
+ "missing; please try re-downloading and reinstalling Wizardry.", x);
}
}
if(bufferedreader != null){
try{
String paragraph = bufferedreader.readLine();
ArrayList<String> page = new ArrayList<String>(1);
int linesPerPage = 16;
int lineNumber = 0;
while(paragraph != null){
// System.out.println(paragraph);
if(paragraph.contains("PAGEBREAK") || lineNumber >= linesPerPage){
text.add(page);
page = new ArrayList<String>(1);
lineNumber = 0;
if(paragraph.contains("PAGEBREAK")) paragraph = bufferedreader.readLine();
}else if(paragraph.contains("LINEBREAK")){
lineNumber++;
page.add("");
paragraph = bufferedreader.readLine();
}else if(paragraph.contains("SECTION")){
sections.add(
new Section(paragraph.replace("SECTION ", ""), text.size() + 1, this.width / 2 + 7,
this.height / 2 - this.ySize / 2 + 14
+ (sections.size() + 2) * this.fontRenderer.FONT_HEIGHT,
nextButtonId++));
paragraph = bufferedreader.readLine();
}else if(paragraph.contains("IMAGE")){
if(paragraph.contains("WORKBENCH")){
this.guiPage = text.size() + 1;
}else if(paragraph.contains("CRYSTAL")){
this.imagePage = text.size() + 1;
}
paragraph = bufferedreader.readLine();
}else{
paragraph = paragraph.replaceAll("NEXT_SPELL_KEY", Keyboard.getKeyName(ClientProxy.NEXT_SPELL.getKeyCode()));
paragraph = paragraph.replaceAll("PREVIOUS_SPELL_KEY", Keyboard.getKeyName(ClientProxy.PREVIOUS_SPELL.getKeyCode()));
paragraph = paragraph.replaceAll("MANA_PER_CRYSTAL_MINUS_30", "" + (Constants.MANA_PER_CRYSTAL - 30));
paragraph = paragraph.replaceAll("MANA_PER_CRYSTAL", "" + Constants.MANA_PER_CRYSTAL);
paragraph = paragraph.replaceAll("BASIC_MAX_CHARGE", "" + Tier.BASIC.maxCharge);
paragraph = paragraph.replaceAll("APPRENTICE_MAX_CHARGE", "" + Tier.APPRENTICE.maxCharge);
paragraph = paragraph.replaceAll("ADVANCED_MAX_CHARGE", "" + Tier.ADVANCED.maxCharge);
paragraph = paragraph.replaceAll("MASTER_MAX_CHARGE", "" + Tier.MASTER.maxCharge);
paragraph = paragraph.replaceAll("BASIC_COLOUR", "\u00A77");
paragraph = paragraph.replaceAll("APPRENTICE_COLOUR", Tier.APPRENTICE.getFormattingCode());
paragraph = paragraph.replaceAll("ADVANCED_COLOUR", Tier.ADVANCED.getFormattingCode());
paragraph = paragraph.replaceAll("MASTER_COLOUR", Tier.MASTER.getFormattingCode());
paragraph = paragraph.replaceAll("FIRE_COLOUR", Element.FIRE.getFormattingCode());
paragraph = paragraph.replaceAll("ICE_COLOUR", Element.ICE.getFormattingCode());
paragraph = paragraph.replaceAll("LIGHTNING_COLOUR", Element.LIGHTNING.getFormattingCode());
paragraph = paragraph.replaceAll("NECROMANCY_COLOUR", Element.NECROMANCY.getFormattingCode());
paragraph = paragraph.replaceAll("EARTH_COLOUR", Element.EARTH.getFormattingCode());
paragraph = paragraph.replaceAll("SORCERY_COLOUR", Element.SORCERY.getFormattingCode());
paragraph = paragraph.replaceAll("HEALING_COLOUR", Element.HEALING.getFormattingCode());
paragraph = paragraph.replaceAll("RESET_COLOUR", "\u00A70");
paragraph = paragraph.replaceAll("MCVERSION", "1.11.2");
paragraph = paragraph.replaceAll("VERSION", Wizardry.VERSION);
int linesInParagraph = this.fontRenderer
.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH).size();
// Ignores empty lines at the top of a page.
if(paragraph.isEmpty() && lineNumber == 0){
paragraph = bufferedreader.readLine();
// Normal paragraph, all on one page
}else if(lineNumber + linesInParagraph <= linesPerPage){
page.add(paragraph);
lineNumber += linesInParagraph;
paragraph = bufferedreader.readLine();
// Paragraphs split across two pages (or more?)
}else{
int linesInFirstPart = linesPerPage - lineNumber;
String paragraphFirstPart = "";
String paragraphLastPart = "";
int i = 0;
List<String> strings = this.fontRenderer.listFormattedStringToWidth(paragraph,
GuiWizardHandbook.PAGE_WIDTH);
for(Object s : strings){
if(i < linesInFirstPart){
paragraphFirstPart = paragraphFirstPart.concat((String)s + " ");
}else{
paragraphLastPart = paragraphLastPart.concat((String)s + " ");
}
i++;
}
// System.out.println("Paragraph crosses page boundary; string split into: \"" +
// paragraphFirstPart + "\" and \"" + paragraphLastPart + "\"");
page.add(paragraphFirstPart);
lineNumber += linesInFirstPart;
paragraph = paragraphLastPart;
}
}
}
text.add(page);
}catch (IOException e){
Wizardry.logger.error("Something went wrong reading file: " + textFilepath
+ ". The file may be damaged; please try re-downloading and reinstalling wizardry.", e);
}
}
}
private class Section {
/** The integer text colour used for the section when it is moused over. Currently orange. */
private static final int HIGHLIGHT_COLOUR = 0xdd4c1d;
String name;
int pageNumber;
int x, y;
int buttonId;
Section(String name, int pageNumber, int x, int y, int id){
this.name = name;
this.pageNumber = pageNumber;
this.x = x;
this.y = y;
this.buttonId = id;
GuiWizardHandbook.this.buttonList.add(new GuiButtonInvisible(id, x, y, GuiWizardHandbook.PAGE_WIDTH,
GuiWizardHandbook.this.fontRenderer.FONT_HEIGHT));
}
void hideButton(){
GuiWizardHandbook.this.buttonList.get(buttonId).visible = false;
}
void drawContents(){
GuiWizardHandbook.this.buttonList.get(buttonId).visible = true;
GuiWizardHandbook.this.fontRenderer.drawString(name, x, y,
GuiWizardHandbook.this.buttonList.get(buttonId).isMouseOver() ? HIGHLIGHT_COLOUR : BLACK);
int nameWidth = GuiWizardHandbook.this.fontRenderer.getStringWidth(name);
String dotsAndNumber = " " + this.pageNumber;
while(GuiWizardHandbook.this.fontRenderer.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH
- nameWidth - 2){
dotsAndNumber = "." + dotsAndNumber;
}
GuiWizardHandbook.this.fontRenderer.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH
- GuiWizardHandbook.this.fontRenderer.getStringWidth(dotsAndNumber), y, BLACK);
}
}
@Override
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
/**
* Fired when a control is clicked. This is the equivalent of ActionListener.actionPerformed(ActionEvent e).
*/
@Override
protected void actionPerformed(GuiButton par1GuiButton){
if(par1GuiButton.enabled){
if(par1GuiButton.id == 0){
if(pageNumber < (text.size() - 1) / 2) pageNumber++;
}else if(par1GuiButton.id == 1){
if(pageNumber > 0) pageNumber--;
}else{
if(pageNumber == 1) pageNumber = (sections.get(par1GuiButton.id - 2).pageNumber - 1) / 2;
}
}
}
/**
* Args: left, top, width, height, pointX, pointY. Note: left, top are local to Gui, pointX, pointY are local to
* screen
*/
protected boolean isPointInRegion(int par1, int par2, int par3, int par4, int par5, int par6){
int k1 = this.width / 2 - xSize / 2;
int l1 = this.height / 2 - this.ySize / 2;
par5 -= k1;
par6 -= l1;
return par5 >= par1 - 1 && par5 < par1 + par3 + 1 && par6 >= par2 - 1 && par6 < par2 + par4 + 1;
}
/**
* Draws a textured rectangle, stretching the section of the image to fit the size given.
*
* @param x The x position of the rectangle
* @param y The y position of the rectangle
* @param u The x position of the top left corner of the section of the image wanted, expressed as a fraction of the
* image width
* @param v The y position of the top left corner of the section of the image wanted, expressed as a fraction of the
* image width
* @param finalWidth The width as rendered
* @param finalHeight The height as rendered
* @param width The width of the section, expressed as a fraction of the image width
* @param height The height of the section, expressed as a fraction of the image width
*/
public static void drawTexturedStretchedRect(int x, int y, int u, int v, int finalWidth, int finalHeight, int width,
int height){
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos((x), y + finalHeight, 0).tex(u, v + height).endVertex();
buffer.pos(x + finalWidth, y + finalHeight, 0).tex(u + width, v + height).endVertex();
buffer.pos(x + finalWidth, (y), 0).tex(u + width, v).endVertex();
buffer.pos((x), (y), 0).tex(u, v).endVertex();
tessellator.draw();
}
private static NonNullList<NonNullList<ItemStack>> createGrid(){
NonNullList<NonNullList<ItemStack>> grid = NonNullList.withSize(3, NonNullList.create());
for(int i=0; i<3; i++){
grid.set(i, NonNullList.withSize(3, ItemStack.EMPTY));
}
return grid;
}
/** Called from init() in the main mod class to initialise the recipes for display in the handbook. */
public static void initDisplayRecipes(){
NonNullList<NonNullList<ItemStack>> craftingGrid;
ItemStack craftingResult;
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.GOLD_NUGGET));
craftingGrid.get(1).set(0, new ItemStack(Blocks.CARPET, 1, 10));
craftingGrid.get(2).set(0, new ItemStack(Items.GOLD_NUGGET));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(1, new ItemStack(Blocks.LAPIS_BLOCK));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(0).set(2, new ItemStack(Blocks.STONE));
craftingGrid.get(1).set(2, new ItemStack(Blocks.STONE));
craftingGrid.get(2).set(2, new ItemStack(Blocks.STONE));
craftingResult = new ItemStack(WizardryBlocks.arcane_workbench);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(1, new ItemStack(Items.STICK));
craftingGrid.get(0).set(2, new ItemStack(Items.GOLD_NUGGET));
craftingResult = new ItemStack(WizardryItems.magic_wand);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(1, new ItemStack(Items.BOOK));
craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingResult = new ItemStack(WizardryItems.spell_book, 1, 1);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.BOOK));
craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingResult = new ItemStack(WizardryItems.wizard_handbook);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(WizardryBlocks.crystal_flower));
craftingResult = new ItemStack(WizardryItems.magic_crystal, 2);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(1, new ItemStack(Items.GLASS_BOTTLE));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_crystal));
craftingResult = new ItemStack(WizardryItems.mana_flask);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(1).set(0, new ItemStack(Blocks.STONE));
craftingGrid.get(0).set(1, new ItemStack(Blocks.STONE));
craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(2, new ItemStack(Blocks.STONE));
craftingGrid.get(2).set(1, new ItemStack(Blocks.STONE));
craftingResult = new ItemStack(WizardryBlocks.transportation_stone, 2);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(1).set(0, new ItemStack(Items.STRING));
craftingGrid.get(0).set(1, new ItemStack(Items.STRING));
craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_crystal));
craftingGrid.get(1).set(2, new ItemStack(Items.STRING));
craftingGrid.get(2).set(1, new ItemStack(Items.STRING));
craftingResult = new ItemStack(WizardryItems.magic_silk, 2);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk));
craftingResult = new ItemStack(WizardryItems.wizard_hat);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_silk));
craftingResult = new ItemStack(WizardryItems.wizard_robe);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_silk));
craftingResult = new ItemStack(WizardryItems.wizard_leggings);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk));
craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk));
craftingResult = new ItemStack(WizardryItems.wizard_boots);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
if(Wizardry.settings.useAlternateScrollRecipe){
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.PAPER));
craftingGrid.get(1).set(0, new ItemStack(Items.STRING));
craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal));
craftingResult = new ItemStack(WizardryItems.blank_scroll);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
}else{
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.PAPER));
craftingGrid.get(1).set(0, new ItemStack(Items.STRING));
craftingResult = new ItemStack(WizardryItems.blank_scroll);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
}
if(Wizardry.settings.firebombIsCraftable){
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.BLAZE_POWDER));
craftingGrid.get(1).set(0, new ItemStack(Items.BLAZE_POWDER));
craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE));
craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER));
craftingResult = new ItemStack(WizardryItems.firebomb, 3);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
}
if(Wizardry.settings.poisonBombIsCraftable){
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.SPIDER_EYE));
craftingGrid.get(1).set(0, new ItemStack(Items.SPIDER_EYE));
craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE));
craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER));
craftingResult = new ItemStack(WizardryItems.poison_bomb, 3);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
}
if(Wizardry.settings.smokeBombIsCraftable){
craftingGrid = createGrid();
craftingGrid.get(0).set(0, new ItemStack(Items.COAL));
craftingGrid.get(1).set(0, new ItemStack(Items.COAL));
craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE));
craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER));
craftingResult = new ItemStack(WizardryItems.smoke_bomb, 3);
RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid));
}
}
}
@@ -5,15 +5,13 @@ import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/**
* Font renderer that renders parts of strings surrounded by '#' (without quotes) in the SGA instead of normal text.
*
* @since Wizardry 1.1
*/
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class MixedFontRenderer extends FontRenderer {
public MixedFontRenderer(GameSettings p_i1035_1_, ResourceLocation p_i1035_2_, TextureManager p_i1035_3_,
@@ -1,52 +0,0 @@
package electroblob.wizardry.client;
import net.minecraft.client.audio.MovingSound;
import net.minecraft.entity.Entity;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that.
@SideOnly(Side.CLIENT)
public class MovingSoundEntity extends MovingSound {
private final Entity source;
private float distance = 0.0F;
public MovingSoundEntity(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){
// Uses BLOCKS because that's the closest thing to inanimate entities. Could use NEUTRAL like
// MovingSoundMinecart.
super(sound, SoundCategory.BLOCKS);
this.source = entity;
this.repeat = repeat;
this.volume = volume;
this.pitch = pitch;
this.repeatDelay = 0;
}
/**
* Updates the JList with a new model.
*/
@Override
public void update(){
if(this.source.isDead && repeat){
this.donePlaying = true;
}else{
this.xPosF = (float)this.source.posX;
this.yPosF = (float)this.source.posY;
this.zPosF = (float)this.source.posZ;
float f = MathHelper.sqrt(this.source.motionX * this.source.motionX
+ this.source.motionY * this.source.motionY + this.source.motionZ * this.source.motionZ);
// Is this something to do with the Doppler effect?
if((double)f >= 0.01D){
this.distance = MathHelper.clamp(this.distance + 0.0025F, 0.0F, 1.0F);
this.volume = 0.0F + MathHelper.clamp(f, 0.0F, 0.5F) * 0.7F;
}else{
// this.pitch = 0.0F;
// this.volume = 0.0F;
}
}
}
}
@@ -1,98 +1,179 @@
package electroblob.wizardry.client;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockMagicLight;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Flight;
import electroblob.wizardry.spell.ShadowWard;
import electroblob.wizardry.spell.Shield;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.spell.*;
import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMerchant;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.RayTraceResult;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderLivingEvent;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraft.util.math.Vec3d;
import net.minecraft.village.MerchantRecipe;
import net.minecraft.world.World;
import net.minecraftforge.client.event.*;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
import java.lang.reflect.Method;
/**
* Event handler responsible for all client-side only events, mostly rendering.
* Event handler responsible for client-side only events, mostly rendering.
*
* @author Electroblob
* @since Wizardry 1.0
*/
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryClientEventHandler {
private static final ResourceLocation shieldTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shield.png");
private static final ResourceLocation wingTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/wing.png");
private static final ResourceLocation shadowWardTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shadow_ward.png");
private static final ResourceLocation sixthSenseTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/sixth_sense.png");
private static final ResourceLocation sixthSenseOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/sixth_sense_overlay.png");
private static final ResourceLocation frostOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_overlay.png");
private static final ResourceLocation blinkOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/blink_overlay.png");
private static final ResourceLocation pointerTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/pointer.png");
private static final ResourceLocation targetPointerTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/target_pointer.png");
/** The remaining time for which the blink screen overlay effect will be displayed in first-person. Since this is
* only for the first-person player (the instance of which is itself stored in a static variable), this can simply
* be stored statically here, rather than needing to be in {@code WizardData}. */
private static int blinkEffectTimer;
/** The number of ticks the blink effect lasts for. */
private static final int BLINK_EFFECT_DURATION = 8;
private static final Method unpressKey;
static {
unpressKey = ObfuscationReflectionHelper.findMethod(KeyBinding.class, "func_74505_d", void.class);
}
/** Starts the first person blink overlay effect. */
public static void playBlinkEffect(){
blinkEffectTimer = BLINK_EFFECT_DURATION;
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL);
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE);
public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
if(event.player == Minecraft.getMinecraft().player){
if(blinkEffectTimer > 0) blinkEffectTimer--;
// Only seems to work here...
// EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player);
// if(victim != null && victim.getHeldItemMainhand().isEmpty()){
// Minecraft.getMinecraft().player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY);
// }
// Reset shaders if their respective potions aren't active
// This is a player so the potion effects are synced by vanilla
if(Minecraft.getMinecraft().entityRenderer.getShaderGroup() != null){
String activeShader = Minecraft.getMinecraft().entityRenderer.getShaderGroup().getShaderGroupName();
if((activeShader.equals(SlowTime.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.slow_time))
|| (activeShader.equals(SixthSense.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense))
|| (activeShader.equals(Transience.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.transience))){
if(activeShader.equals(SixthSense.SHADER.toString())
|| activeShader.equals(Transience.SHADER.toString())) playBlinkEffect();
Minecraft.getMinecraft().entityRenderer.stopUseShader();
}
}
}
}
@SubscribeEvent
public static void onRenderHandEvent(RenderHandEvent event){
EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player);
if(victim != null){
victim.rotationYawHead = Minecraft.getMinecraft().player.rotationYaw;
if(Minecraft.getMinecraft().player.getHeldItemMainhand().isEmpty()){
event.setCanceled(true);
}
}
}
// Shift-scrolling to change spells
// This event is called every tick, not just when a movement key is pressed
@SubscribeEvent
public static void onInputUpdateEvent(InputUpdateEvent event){
// Prevents the player moving when paralysed
if(event.getEntityPlayer().isPotionActive(WizardryPotions.paralysis)){
event.getMovementInput().moveForward = 0;
event.getMovementInput().moveStrafe = 0;
event.getMovementInput().jump = false;
event.getMovementInput().sneak = false;
}
}
@SubscribeEvent
public static void onClientTickEvent(TickEvent.ClientTickEvent event){
if(event.phase == TickEvent.Phase.END && !net.minecraft.client.Minecraft.getMinecraft().isGamePaused()){
World world = net.minecraft.client.Minecraft.getMinecraft().world;
if(world == null) return;
for(TileEntity tileentity : world.loadedTileEntityList){
if(tileentity instanceof TileEntityDispenser){
if(DispenserCastingData.get((TileEntityDispenser)tileentity) != null){
DispenserCastingData.get((TileEntityDispenser)tileentity).update();
}
}
}
SpellEmitterData.update(world);
}
}
@SubscribeEvent
public static void onMouseEvent(MouseEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
&& Wizardry.settings.enableShiftScrolling){
// Prevents the player looking around when paralysed
if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.paralysis)
&& Minecraft.getMinecraft().inGameHasFocus){
event.setCanceled(true);
if(event.getDwheel() > 0){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}else if(event.getDwheel() < 0){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}
Minecraft.getMinecraft().player.prevRotationYaw = 0;
Minecraft.getMinecraft().player.prevRotationPitch = 0;
Minecraft.getMinecraft().player.rotationYaw = 0;
Minecraft.getMinecraft().player.rotationPitch = 0;
}
}
@@ -114,23 +195,51 @@ public final class WizardryClientEventHandler {
event.setNewfov(event.getFov() * 1.0F - maxUseSeconds * 0.15F);
}
if(blinkEffectTimer > 0){
float f = ((float)Math.max(blinkEffectTimer - 2, 0))/BLINK_EFFECT_DURATION;
event.setNewfov(event.getFov() + f * f * 0.7f);
}
}
// Third person
@SubscribeEvent
public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){
renderShieldIfActive(event.getEntityPlayer());
renderWingsIfActive(event.getEntityPlayer(), event.getPartialRenderTick());
renderShadowWardIfActive(event.getEntityPlayer());
public static void onDrawBlockHighlightEvent(DrawBlockHighlightEvent event){
// Hide the block outline for magic light blocks unless the player can dispel them
if(event.getTarget().typeOfHit == RayTraceResult.Type.BLOCK
&& event.getPlayer().world.getBlockState(event.getTarget().getBlockPos()).getBlock() instanceof BlockMagicLight){
if((!(event.getPlayer().getHeldItemMainhand().getItem() instanceof ISpellCastingItem)
&& !(event.getPlayer().getHeldItemOffhand().getItem() instanceof ISpellCastingItem))
|| !ItemArtefact.isArtefactActive(event.getPlayer(), WizardryItems.charm_light)){
event.setCanceled(true);
}
}
}
// First person
// Brute-force fix for crystals not showing up when a wizard is given a spell book in the trade GUI.
@SubscribeEvent
public static void onRenderWorldLastEvent(RenderWorldLastEvent event){
// Now only fires in first person.
if(Minecraft.getMinecraft().gameSettings.thirdPersonView == 0){
renderShieldFirstPerson(Minecraft.getMinecraft().player);
renderShadowWardFirstPerson(Minecraft.getMinecraft().player);
public static void onGuiDrawForegroundEvent(GuiContainerEvent.DrawForeground event){
if(event.getGuiContainer() instanceof GuiMerchant){
GuiMerchant gui = (GuiMerchant)event.getGuiContainer();
// Note that gui.getMerchant() returns an NpcMerchant, not an EntityWizard.
// Using == the specific item rather than instanceof because that's how trades do it.
if(gui.inventorySlots.getSlot(0).getStack().getItem() == WizardryItems.spell_book
|| gui.inventorySlots.getSlot(1).getStack().getItem() == WizardryItems.spell_book){
for(MerchantRecipe trade : gui.getMerchant().getRecipes(Minecraft.getMinecraft().player)){
if(trade.getItemToBuy().getItem() == WizardryItems.spell_book && trade.getSecondItemToBuy().isEmpty()){
Slot slot = gui.inventorySlots.getSlot(2);
// It still doesn't look quite right because the slot highlight is behind the item, but it'll do
// until/unless I find a better solution.
DrawingUtils.drawItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(),
gui.getSlotUnderMouse() == slot);
}
}
}
}
}
@@ -138,60 +247,65 @@ public final class WizardryClientEventHandler {
public static void onRenderLivingEvent(RenderLivingEvent.Post<EntityLivingBase> event){
Minecraft mc = Minecraft.getMinecraft();
WizardData properties = WizardData.get(mc.player);
RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16);
WizardData data = WizardData.get(mc.player);
RenderManager renderManager = event.getRenderer().getRenderManager();
ItemStack wand = mc.player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
if(!(wand.getItem() instanceof ISpellCastingItem)){
wand = mc.player.getHeldItemOffhand();
}
// Target selection pointer
if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && rayTrace != null && !(event.getEntity() instanceof EntityArmorStand)
&& rayTrace.entityHit == event.getEntity() && properties != null && properties.selectedMinion != null){
if(mc.player.isSneaking() && wand.getItem() instanceof ISpellCastingItem && WizardryUtilities.isLiving(event.getEntity())
&& data != null && data.selectedMinion != null){
// -> Moved this in here so it isn't called every tick
RayTraceResult rayTrace = RayTracer.standardEntityRayTrace(mc.world, mc.player, 16, false);
if(rayTrace != null && rayTrace.entityHit == event.getEntity()){
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.pushMatrix();
GlStateManager.pushMatrix();
GlStateManager.disableCull();
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// Disabling depth test allows it to be seen through everything.
GL11.glDisable(GL11.GL_DEPTH_TEST);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableCull();
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// Disabling depth test allows it to be seen through everything.
GlStateManager.disableDepth();
GlStateManager.color(1, 1, 1, 1);
GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ());
GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ());
// This counteracts the reverse rotation behaviour when in front f5 view.
// Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example.
float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX;
GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F);
// This counteracts the reverse rotation behaviour when in front f5 view.
// Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example.
float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX;
GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
mc.renderEngine.bindTexture(targetPointerTexture);
mc.renderEngine.bindTexture(targetPointerTexture);
buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex();
buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex();
buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex();
buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex();
buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex();
buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex();
buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex();
buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex();
tessellator.draw();
tessellator.draw();
GlStateManager.enableCull();
GlStateManager.enableLighting();
GL11.glEnable(GL11.GL_DEPTH_TEST);
GlStateManager.enableCull();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
GlStateManager.popMatrix();
GlStateManager.popMatrix();
}
}
// Summoned creature selection pointer
if(properties != null && properties.selectedMinion != null && properties.selectedMinion.get() == event.getEntity()){
if(data != null && data.selectedMinion != null && data.selectedMinion.get() == event.getEntity()){
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
@@ -202,7 +316,7 @@ public final class WizardryClientEventHandler {
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// Disabling depth test allows it to be seen through everything.
GL11.glDisable(GL11.GL_DEPTH_TEST);
GlStateManager.disableDepth();
GlStateManager.color(1, 1, 1, 1);
GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ());
@@ -226,14 +340,15 @@ public final class WizardryClientEventHandler {
GlStateManager.enableCull();
GlStateManager.enableLighting();
GL11.glEnable(GL11.GL_DEPTH_TEST);
GlStateManager.enableDepth();
GlStateManager.popMatrix();
}
// Sixth sense
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) < 20
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()
* (1 + mc.player.getActivePotionEffect(WizardryPotions.sixth_sense).getAmplifier() * Constants.RANGE_INCREASE_PER_LEVEL)){
Tessellator tessellator = Tessellator.getInstance();
@@ -247,7 +362,7 @@ public final class WizardryClientEventHandler {
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// Disabling depth test allows it to be seen through everything.
GL11.glDisable(GL11.GL_DEPTH_TEST);
GlStateManager.disableDepth();
GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height * 0.6, event.getZ());
@@ -271,7 +386,7 @@ public final class WizardryClientEventHandler {
GlStateManager.enableCull();
GlStateManager.disableBlend();
GlStateManager.enableLighting();
GL11.glEnable(GL11.GL_DEPTH_TEST);
GlStateManager.enableDepth();
GlStateManager.popMatrix();
}
@@ -280,384 +395,72 @@ public final class WizardryClientEventHandler {
@SubscribeEvent
public static void onRenderGameOverlayEvent(RenderGameOverlayEvent.Post event){
if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET
&& Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense)){
GlStateManager.pushMatrix();
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glDepthMask(false);
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.disableAlpha();
Minecraft.getMinecraft().renderEngine.bindTexture(sixthSenseOverlayTexture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0.0D, (double)event.getResolution().getScaledHeight(), -90.0D).tex(0.0D, 1.0D).endVertex();
buffer.pos((double)event.getResolution().getScaledWidth(), (double)event.getResolution().getScaledHeight(), -90.0D).tex(1.0D, 1.0D)
.endVertex();
buffer.pos((double)event.getResolution().getScaledWidth(), 0.0D, -90.0D).tex(1.0D, 0.0D).endVertex();
buffer.pos(0.0D, 0.0D, -90.0D).tex(0.0D, 0.0D).endVertex();
tessellator.draw();
GL11.glDepthMask(true);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GlStateManager.enableAlpha();
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.popMatrix();
}
if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET && Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.frost)){
GlStateManager.pushMatrix();
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glDepthMask(false);
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.disableAlpha();
Minecraft.getMinecraft().renderEngine.bindTexture(frostOverlayTexture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0.0D, (double)event.getResolution().getScaledHeight(), -90.0D).tex(0.0D, 1.0D).endVertex();
buffer.pos((double)event.getResolution().getScaledWidth(), (double)event.getResolution().getScaledHeight(), -90.0D).tex(1.0D, 1.0D)
.endVertex();
buffer.pos((double)event.getResolution().getScaledWidth(), 0.0D, -90.0D).tex(1.0D, 0.0D).endVertex();
buffer.pos(0.0D, 0.0D, -90.0D).tex(0.0D, 0.0D).endVertex();
tessellator.draw();
GL11.glDepthMask(true);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GlStateManager.enableAlpha();
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.popMatrix();
if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET){
if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense)){
OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableAlpha();
renderScreenOverlay(event, sixthSenseOverlayTexture);
GlStateManager.enableAlpha();
GlStateManager.color(1, 1, 1, 1);
}
if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.frost)){
OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableAlpha();
renderScreenOverlay(event, frostOverlayTexture);
GlStateManager.enableAlpha();
GlStateManager.color(1, 1, 1, 1);
}
if(blinkEffectTimer > 0){
float alpha = ((float)blinkEffectTimer)/BLINK_EFFECT_DURATION;
OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ZERO);
GlStateManager.color(1, 1, 1, alpha);
GlStateManager.disableAlpha();
renderScreenOverlay(event, blinkOverlayTexture);
GlStateManager.enableAlpha();
GlStateManager.color(1, 1, 1, 1);
}
}
}
private static void renderScreenOverlay(RenderGameOverlayEvent.Post event, ResourceLocation texture){
GlStateManager.pushMatrix();
// FIXME: Something in here is making the first person shadow ward rather translucent.
private static void renderShadowWardFirstPerson(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer) != null && WizardData.get(entityplayer).currentlyCasting() instanceof ShadowWard
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard)){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableLighting();
GlStateManager.disableAlpha();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.translate(0, 1.2, 0);
GlStateManager.rotate(-entityplayer.rotationYaw, 0, 1, 0);
GlStateManager.rotate(entityplayer.rotationPitch, 1, 0, 0);
Minecraft.getMinecraft().renderEngine.bindTexture(shadowWardTexture);
GlStateManager.pushMatrix();
GlStateManager.translate(0, 0, 1.2);
GlStateManager.rotate(entityplayer.world.getWorldTime() * -2, 0, 0, 1);
GlStateManager.scale(1.1, 1.1, 1.1);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableLighting();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
private static void renderShadowWardIfActive(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer).currentlyCasting() instanceof ShadowWard
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard)){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.rotate(180, 0, 1, 0);
GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0);
Minecraft.getMinecraft().renderEngine.bindTexture(shadowWardTexture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.translate(0, 1.2, 0);
GlStateManager.rotate(entityplayer.world.getWorldTime() * -2, 0, 0, 1);
GlStateManager.scale(1.1, 1.1, 1.1);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.enableLighting();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
private static void renderWingsIfActive(EntityPlayer entityplayer, float partialTickTime){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer).currentlyCasting() instanceof Flight
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof Flight)){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// GlStateManager.rotate(-entityplayer.rotationYawHead, 0, 1, 0);
GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0);
// GlStateManager.rotate(180, 1, 0, 0);
Minecraft.getMinecraft().renderEngine.bindTexture(wingTexture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.pushMatrix();
GlStateManager.translate(0.1, 0.4, -0.15);
GlStateManager.rotate(20 + 20 * (float)Math.sin(entityplayer.world.getWorldTime() * 0.3), 0, 1, 0);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.pushMatrix();
GlStateManager.translate(-0.1, 0.4, -0.15);
GlStateManager.rotate(-200 - 20 * (float)Math.sin(entityplayer.world.getWorldTime() * 0.3), 0, 1, 0);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
private static void renderShieldFirstPerson(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer) != null && WizardData.get(entityplayer).shield != null
&& (WizardData.get(entityplayer).currentlyCasting() instanceof Shield
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof Shield))){
GlStateManager.pushMatrix();
GlStateManager.disableCull();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.translate(0, 1.4, 0);
GlStateManager.rotate(-entityplayer.rotationYaw, 0, 1, 0);
GlStateManager.rotate(entityplayer.rotationPitch, 1, 0, 0);
GlStateManager.translate(0, 0, 0.8);
Tessellator tessellator = Tessellator.getInstance();
Minecraft.getMinecraft().renderEngine.bindTexture(shieldTexture);
renderShield(tessellator);
GlStateManager.enableLighting();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableCull();
GlStateManager.disableBlend();
// RenderHelper.enableStandardItemLighting();
GlStateManager.popMatrix();
}
}
private static void renderShieldIfActive(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer).shield != null && (WizardData.get(entityplayer).currentlyCasting() instanceof Shield
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof Shield))){
GlStateManager.pushMatrix();
GlStateManager.disableCull();
GlStateManager.enableBlend();
// For some reason, the old blend function (GL11.GL_SRC_ALPHA, GL11.GL_SRC_ALPHA) caused the inner
// edges to appear black, so I have changed it to this, which looks very slightly different.
GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.translate(0, 1.3, 0);
// GlStateManager.rotate(180, 0, 1, 0);
GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0);
// GlStateManager.rotate(-entityplayer.rotationPitch, 1, 0, 0);
GlStateManager.translate(0, 0, 0.8);
Tessellator tessellator = Tessellator.getInstance();
Minecraft.getMinecraft().renderEngine.bindTexture(shieldTexture);
renderShield(tessellator);
GlStateManager.enableLighting();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableCull();
GlStateManager.disableBlend();
// RenderHelper.enableStandardItemLighting();
GlStateManager.popMatrix();
}
}
private static void renderShield(Tessellator tessellator){
GlStateManager.disableDepth();
GlStateManager.depthMask(false);
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
double widthOuter = 0.6d;
double heightOuter = 0.7d;
double widthInner = 0.3d;
double heightInner = 0.4d;
double depth = 0.2d;
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthInner, heightOuter, -depth).tex(0.2, 0).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, heightOuter, -depth).tex(0.8, 0).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthOuter, heightInner, -depth).tex(1, 0.2).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthOuter, -heightInner, -depth).tex(1, 0.8).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, -heightOuter, -depth).tex(0.8, 1).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthInner, -heightOuter, -depth).tex(0.2, 1).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthOuter, -heightInner, -depth).tex(0, 0.8).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0.0D, (double)event.getResolution().getScaledHeight(), -90.0D).tex(0.0D, 1.0D).endVertex();
buffer.pos((double)event.getResolution().getScaledWidth(), (double)event.getResolution().getScaledHeight(), -90.0D).tex(1.0D, 1.0D)
.endVertex();
buffer.pos((double)event.getResolution().getScaledWidth(), 0.0D, -90.0D).tex(1.0D, 0.0D).endVertex();
buffer.pos(0.0D, 0.0D, -90.0D).tex(0.0D, 0.0D).endVertex();
tessellator.draw();
GlStateManager.depthMask(true);
GlStateManager.enableDepth();
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
}
}
@@ -0,0 +1,121 @@
package electroblob.wizardry.client;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
/** Event handler class responsible for handling wizardry's controls. */
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class WizardryControlHandler {
static boolean NkeyPressed = false;
static boolean BkeyPressed = false;
// Changed to a tick event to allow mouse button keybinds
// The 'lag' that happened previously was actually because the code only fired when a keyboard key was pressed!
@SubscribeEvent
public static void onTickEvent(TickEvent.ClientTickEvent event){
if(event.phase == TickEvent.Phase.END) return; // Only really needs to be once per tick
if(Wizardry.proxy instanceof ClientProxy){
EntityPlayer player = Minecraft.getMinecraft().player;
if(player != null){
ItemStack wand = getWandInUse(player);
if(wand == null) return;
if(ClientProxy.NEXT_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
if(!NkeyPressed){
NkeyPressed = true;
selectNextSpell(wand);
}
}else{
NkeyPressed = false;
}
if(ClientProxy.PREVIOUS_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
if(!BkeyPressed){
BkeyPressed = true;
// Packet building
selectPreviousSpell(wand);
}
}else{
BkeyPressed = false;
}
}
}
}
// Shift-scrolling to change spells
@SubscribeEvent
public static void onMouseEvent(MouseEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = getWandInUse(player);
if(wand == null) return;
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
&& Wizardry.settings.shiftScrolling){
event.setCanceled(true);
int d = Wizardry.settings.reverseScrollDirection ? -event.getDwheel() : event.getDwheel();
if(d > 0){
selectNextSpell(wand);
}else if(d < 0){
selectPreviousSpell(wand);
}
}
}
private static ItemStack getWandInUse(EntityPlayer player){
ItemStack wand = player.getHeldItemMainhand();
// Only bother sending packets if the player is holding a spellcasting item with more than one spell slot
if(!(wand.getItem() instanceof ISpellCastingItem) || ((ISpellCastingItem)wand.getItem()).getSpells(wand).length < 2){
wand = player.getHeldItemOffhand();
if(!(wand.getItem() instanceof ISpellCastingItem) || ((ISpellCastingItem)wand.getItem()).getSpells(wand).length < 2) return null;
}
return wand;
}
private static void selectNextSpell(ItemStack wand){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
// GUI switch animation
((ISpellCastingItem)wand.getItem()).selectNextSpell(wand); // Makes sure the spell is set immediately for the client
GuiSpellDisplay.playSpellSwitchAnimation(true);
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1));
}
private static void selectPreviousSpell(ItemStack wand){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
// GUI switch animation
((ISpellCastingItem)wand.getItem()).selectPreviousSpell(wand); // Makes sure the spell is set immediately for the client
GuiSpellDisplay.playSpellSwitchAnimation(false);
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1));
}
}
@@ -0,0 +1,35 @@
package electroblob.wizardry.client.audio;
import net.minecraft.client.audio.MovingSound;
import net.minecraft.entity.Entity;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that.
//@SideOnly(Side.CLIENT)
public class MovingSoundEntity<T extends Entity> extends MovingSound {
protected final T source;
protected float distance = 0.0F;
public MovingSoundEntity(T entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
super(sound, category);
this.source = entity;
this.repeat = repeat;
this.volume = volume;
this.pitch = pitch;
this.repeatDelay = 0;
}
@Override
public void update(){
if(this.source.isDead){
this.donePlaying = true;
}else{
this.xPosF = (float)this.source.posX;
this.yPosF = (float)this.source.posY;
this.zPosF = (float)this.source.posZ;
}
}
}
@@ -0,0 +1,94 @@
package electroblob.wizardry.client.audio;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
import net.minecraft.util.ITickable;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
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 java.util.HashSet;
import java.util.Set;
/**
* Instances of this class represent a set of sounds which together form a looped sound: start, loop and end.
* Currently this is only used for continuous spell sounds in wizardry itself, but feel free to make your own
* implementations - this class will take care of the internals.
*
* @since Wizardry 4.2
* @author Electroblob
*/
// See MusicTicker, this is the same idea of just storing the sounds statically client-side and ticking them
@Mod.EventBusSubscriber(Side.CLIENT)
public abstract class SoundLoop implements ITickable {
private static final Set<SoundLoop> activeLoops = new HashSet<>();
private final ISound start;
private final ISound loop;
private final ISound end;
private boolean looping = false;
private boolean needsRemoving = false;
public SoundLoop(SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, ISoundFactory factory){
// The reason I've gone to the effort of having a factory for these is that we need SoundLoop to have control
// over which sounds are repeated and which aren't whilst keeping them private.
this.start = factory.create(start, category, false);
this.loop = factory.create(loop, category, true);
this.end = factory.create(end, category, false);
}
@Override
public void update(){
// Check every tick if the start sound is done playing and if so, start the loop sound
if(!looping && !Minecraft.getMinecraft().getSoundHandler().isSoundPlaying(start)){
Minecraft.getMinecraft().getSoundHandler().playSound(loop);
looping = true;
}
}
/** Stops the loop part of the sound immediately and starts playing the end part. This may be called from subclasses
* or externally depending on the implementation. */
// For continuous spell sounds it's internally
public void endLoop(){
Minecraft.getMinecraft().getSoundHandler().stopSound(start);
Minecraft.getMinecraft().getSoundHandler().stopSound(loop);
Minecraft.getMinecraft().getSoundHandler().playSound(end);
// Can't modify activeLoops directly since we'll probably be calling this method from update(), which is
// during iteration of activeLoops so it could cause a ConcurrentModificationException
this.markForRemoval();
}
/** Marks this sound loop to be removed next tick. */
protected void markForRemoval(){
this.needsRemoving = true;
}
// Static methods
public static void addLoop(SoundLoop loop){
activeLoops.add(loop);
// Do this here rather than in the constructor in case someone wants to play the loop later or reuse it
Minecraft.getMinecraft().getSoundHandler().playSound(loop.start);
}
@SubscribeEvent
public static void tick(TickEvent.ClientTickEvent event){
// Using the END phase means we can check for stopped sounds as soon as they are stopped (effectively),
// meaning we don't get the 'cut' between the start and loop sounds
// FIXME: Apparently this only works for dispensers. What the heck is the difference?!
if(event.phase == TickEvent.Phase.END){
activeLoops.forEach(SoundLoop::update);
activeLoops.removeIf(s -> s.needsRemoving);
}
}
@FunctionalInterface
public interface ISoundFactory {
ISound create(SoundEvent sound, SoundCategory category, boolean repeat);
}
}
@@ -0,0 +1,117 @@
package electroblob.wizardry.client.audio;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.audio.PositionedSound;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
/** Abstract base class for sound loops associated with spells; see subclasses below for implementations. */
public abstract class SoundLoopSpell extends SoundLoop {
private final Spell spell;
public SoundLoopSpell(SoundEvent start, SoundEvent loop, SoundEvent end, ISoundFactory factory, Spell spell){
super(start, loop, end, WizardrySounds.SPELLS, factory);
this.spell = spell;
}
@Override
public void update(){
// This may be a bit overkill but I might as well put functionality in superclasses where possible
if(stillCasting(spell)){
// This can't called in the same tick as endLoop because otherwise, if the spell casting stops during the
// same tick as the transition to the loop sound it will try and stop the loop immediately after it has
// started - and for whatever reason, this causes the 'Channel null in method stop' error.
super.update();
}else{
endLoop();
}
}
protected abstract boolean stillCasting(Spell spell);
/** Implements a sound loop for continuous spells cast by entities. */
public static class SoundLoopSpellEntity extends SoundLoopSpell {
private final EntityLivingBase source;
public SoundLoopSpellEntity(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, EntityLivingBase source, float volume, float pitch){
super(start, loop, end, (sound, category, repeat) -> new MovingSoundEntity<>(source, sound, category, volume, pitch, repeat), spell);
this.source = source;
}
@Override
protected boolean stillCasting(Spell spell){
return WizardryUtilities.isCasting(source, spell);
}
}
public static abstract class SoundLoopSpellPosition extends SoundLoopSpell {
public SoundLoopSpellPosition (SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell,
double x, double y, double z, float sndVolume, float sndPitch){
// Huh, I actually found a use for a non-static initialiser block - hence the double curly brackets...
super(start, loop, end, (sound, category, r) -> new PositionedSound(sound, category){{
// ...et voila, we can just set protected fields as we please using external variables
this.xPosF = (float)x;
this.yPosF = (float)y;
this.zPosF = (float)z;
this.repeat = r;
this.volume = sndVolume;
this.pitch = sndPitch;
}}, spell);
}
}
/** Implements a sound loop for continuous spells cast by dispensers. */
public static class SoundLoopSpellDispenser extends SoundLoopSpellPosition {
private final TileEntityDispenser source;
public SoundLoopSpellDispenser(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, World world,
double x, double y, double z, float sndVolume, float sndPitch){
super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
TileEntity tileentity = world.getTileEntity(new BlockPos(x, y, z));
if(tileentity instanceof TileEntityDispenser) this.source = (TileEntityDispenser)tileentity;
else throw new NullPointerException(String.format("Playing continuous spell sound: no dispenser found at %s, %s, %s", x, y, z));
}
@Override
protected boolean stillCasting(Spell spell){
return DispenserCastingData.get(source).currentlyCasting() == spell;
}
}
/** Implements a sound loop for continuous spells cast at a position (via commands). */
public static class SoundLoopSpellPosTimed extends SoundLoopSpellPosition {
private int timeLeft;
public SoundLoopSpellPosTimed(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, int duration,
double x, double y, double z, float sndVolume, float sndPitch){
super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
this.timeLeft = duration;
}
@Override
public void update(){
super.update();
timeLeft--;
}
@Override
protected boolean stillCasting(Spell spell){
return timeLeft > 0;
}
}
}
@@ -0,0 +1,449 @@
package electroblob.wizardry.client.gui;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.IManaStoringItem;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.IWorkbenchItem;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.input.Keyboard;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiArcaneWorkbench extends GuiContainer {
private GuiButton applyBtn;
public static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID,
"textures/gui/arcane_workbench.png");
private IInventory playerInventory;
private IInventory arcaneWorkbenchInventory;
private static final int TOOLTIP_WIDTH = 164;
/** We report the actual size of the GUI to Minecraft when a wand is in so JEI doesn't overdraw it.
* For calculations, we use the size without the tooltip, which is stored in this constant. */
private static final int MAIN_GUI_WIDTH = 176;
private static final int RUNE_LEFT = 38;
private static final int RUNE_TOP = 22;
private static final int RUNE_WIDTH = 100;
private static final int RUNE_HEIGHT = 100;
private static final int HALO_DIAMETER = 156;
private static final int TEXTURE_WIDTH = 512;
private static final int TEXTURE_HEIGHT = 256;
private int animationTimer = 0;
private static final int ANIMATION_DURATION = 20;
public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){
super(new ContainerArcaneWorkbench(invPlayer, entity));
this.playerInventory = invPlayer;
this.arcaneWorkbenchInventory = entity;
xSize = MAIN_GUI_WIDTH;
ySize = 220;
}
// Huh, didn't realise this method existed. Pretty neat.
@Override
public void updateScreen(){
if(animationTimer > 0) animationTimer--;
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks){
this.drawDefaultBackground();
GlStateManager.color(1, 1, 1, 1); // Just in case
Slot slot = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT);
// Tests if there is a wand in the workbench and edits the positioning accordingly
if(slot.getHasStack() && slot.getStack().getItem() instanceof IWorkbenchItem
&& ((IWorkbenchItem)slot.getStack().getItem()).showTooltip(slot.getStack())){
xSize = MAIN_GUI_WIDTH + TOOLTIP_WIDTH;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = (this.width - TOOLTIP_WIDTH) / 2 + 64;
}else{
xSize = MAIN_GUI_WIDTH;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = this.width / 2 + 64;
}
this.applyBtn.enabled = slot.getHasStack();
super.drawScreen(mouseX, mouseY, partialTicks);
// Required now, or item mouseover tooltips won't render.
this.renderHoveredToolTip(mouseX, mouseY);
}
@Override
public void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY){
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Animation
// Grey background
DrawingUtils.drawTexturedRect(guiLeft + RUNE_LEFT, guiTop + RUNE_TOP, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, 0,
RUNE_WIDTH, RUNE_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// Yellow 'halo'
if(animationTimer > 0){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
int x = guiLeft + RUNE_LEFT + RUNE_WIDTH/2;
int y = guiTop + RUNE_TOP + RUNE_HEIGHT/2;
float scale = (animationTimer + partialTicks)/ANIMATION_DURATION;
scale = (float)(1 - Math.pow(1-scale, 1.4f)); // Makes it slower at the start and speed up
GlStateManager.scale(scale, scale, 1);
GlStateManager.translate(x/scale, y/scale, 0);
DrawingUtils.drawTexturedRect(-HALO_DIAMETER /2, -HALO_DIAMETER /2, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, RUNE_HEIGHT,
HALO_DIAMETER, HALO_DIAMETER, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
// Main inventory
DrawingUtils.drawTexturedRect(guiLeft, guiTop, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT);
float opacity = (animationTimer + partialTicks)/ANIMATION_DURATION;
// Changing slots
for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){
Slot slot = this.inventorySlots.getSlot(i);
if(slot.xPos >= 0 && slot.yPos >= 0){
// Slot background
DrawingUtils.drawTexturedRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 0, 220, 36, 36, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// Slot animation
// IDEA: Somehow replace with intelligent check for whether the spell actually got applied
if(animationTimer > 0 && slot.getHasStack()){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1, opacity);
DrawingUtils.drawTexturedRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 36, 220, 36, 36, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
}
// Crystal + upgrade slot animations
if(animationTimer > 0){
Slot crystals = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CRYSTAL_SLOT);
Slot upgrades = this.inventorySlots.getSlot(ContainerArcaneWorkbench.UPGRADE_SLOT);
if(crystals.getHasStack()){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1, opacity);
DrawingUtils.drawTexturedRect(guiLeft + crystals.xPos - 8, guiTop + crystals.yPos - 8,
MAIN_GUI_WIDTH + TOOLTIP_WIDTH + RUNE_WIDTH, 0, 32, 32, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
if(upgrades.getHasStack()){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1, opacity);
DrawingUtils.drawTexturedRect(guiLeft + upgrades.xPos - 8, guiTop + upgrades.yPos - 8,
MAIN_GUI_WIDTH + TOOLTIP_WIDTH + RUNE_WIDTH, 0, 32, 32, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
// Tooltip only drawn if there is a wand
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack();
if(!(stack.getItem() instanceof IWorkbenchItem)){
Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!");
return;
}
if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){
// Tooltip box
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH, guiTop, MAIN_GUI_WIDTH, 0, TOOLTIP_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT);
int y = guiTop + 20;
if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){
y += 14;
}
if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){
Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack);
GlStateManager.enableBlend();
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.isCreative() && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
// As of Wizardry 1.2, the icons have been split off into their own texture files to allow for add-on
// mods to add their own.
Minecraft.getMinecraft().renderEngine
.bindTexture(discovered ? spell.getElement().getIcon() : Element.MAGIC.getIcon());
// Renders the little element icon
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + 5, y, 8, 8);
y += 10;
}
}
GlStateManager.disableBlend();
int x = 0;
y += 16;
// Look how much shorter this is with the WandHelper class!
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(stack, item);
if(level > 0){
ItemStack stack1 = new ItemStack(item, level);
GlStateManager.enableDepth();
this.itemRender.renderItemAndEffectIntoGUI(stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y);
this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y,
null);
x += 18;
GlStateManager.disableDepth();
}
}
}
}
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Fixes the bug that caused the slot highlight to render opaque. I don't know why it works, it just works!
GlStateManager.disableBlend();
GlStateManager.enableAlpha();
}
@Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){
GlStateManager.color(1, 1, 1, 1); // Just in case
this.fontRenderer
.drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName()
: I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752);
this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName()
: I18n.format(this.playerInventory.getName()), 8, this.ySize - 96 + 2, 4210752);
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack();
if(!(stack.getItem() instanceof IWorkbenchItem)){
Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!");
return;
}
if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){
int y = 6;
this.fontRenderer.drawStringWithShadow("\u00A7f" + stack.getDisplayName(), MAIN_GUI_WIDTH + 6, y, 0);
if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){
y += 14;
this.fontRenderer.drawStringWithShadow(
"\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana")
+ " " + ((IManaStoringItem)stack.getItem()).getMana(stack) + "/"
+ ((IManaStoringItem)stack.getItem()).getManaCapacity(stack),
MAIN_GUI_WIDTH + 6, y, 0);
}
y += 14;
if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){
Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack);
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.isCreative() && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
if(discovered){
this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), MAIN_GUI_WIDTH + 16, y, 0);
}else{
this.mc.standardGalacticFontRenderer.drawStringWithShadow(
"\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), MAIN_GUI_WIDTH + 16, y, 0);
}
y += 10;
}
}
if(WandHelper.getTotalUpgrades(stack) > 0){
y += 6;
this.fontRenderer.drawStringWithShadow("\u00A7f" + I18n.format("container."
+ Wizardry.MODID + ":arcane_workbench.upgrades"), MAIN_GUI_WIDTH + 6, y, 0);
int x = 0;
y += 10;
// Wand upgrade tooltips
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(stack, item);
if(level > 0){
// The javadoc for isPointInRegion is ambiguous; what it means is that the REGION is
// relative to the GUI but the POINT isn't.
if(isPointInRegion(MAIN_GUI_WIDTH + 6 + x, y, 16, 16, mouseX, mouseY)){
ItemStack stack1 = new ItemStack(item, level);
this.renderToolTip(stack1, mouseX - guiLeft, mouseY - guiTop);
}
x += 18;
}
}
}
}
}
}
@Override
public void initGui(){
this.mc.player.openContainer = this.inventorySlots;
this.guiLeft = (this.width - this.xSize) / 2;
this.guiTop = (this.height - this.ySize) / 2;
Keyboard.enableRepeatEvents(true);
this.buttonList.clear();
this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 64, this.height / 2 + 3));
}
@Override
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
@Override
protected void actionPerformed(GuiButton button){
if(button.enabled){
if(button.id == 0){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON);
WizardryPacketHandler.net.sendToServer(msg);
// Sound
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(
WizardrySounds.BLOCK_ARCANE_WORKBENCH_SPELLBIND, 1));
// Animation
animationTimer = 20;
}
}
}
private class GuiButtonApply extends GuiButton {
public GuiButtonApply(int id, int x, int y){
super(id, x, y, 16, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply"));
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
// Whether the button is highlighted
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
int k = 72;
int l = 220;
//int colour = 14737632;
if(this.enabled){
if(this.hovered){
k += this.width * 2;
//colour = 16777120;
}
}else{
k += this.width;
//colour = 10526880;
}
DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 512, 256);
//this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2,
// this.y + (this.height - 8) / 2, colour);
}
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL);
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE);
}
}
@@ -1,12 +1,10 @@
package electroblob.wizardry.client;
package electroblob.wizardry.client.gui;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
class GuiButtonInvisible extends GuiButton {
//@SideOnly(Side.CLIENT)
public class GuiButtonInvisible extends GuiButton {
public GuiButtonInvisible(int id, int x, int y, int width, int height){
super(id, x, y, width, height, "");
@@ -0,0 +1,90 @@
package electroblob.wizardry.client.gui;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Resurrection;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiGameOver;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraftforge.client.event.GuiScreenEvent;
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;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiButtonResurrect extends GuiButton {
private static int timeSinceDeath = -1;
private final String translationKey;
public GuiButtonResurrect(int id, int x, int y, String translationKey){
super(id, x, y, I18n.format(translationKey + "_wait", Resurrection.getRemainingWaitTime(timeSinceDeath)));
this.translationKey = translationKey;
}
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTicks){
int waitTime = Resurrection.getRemainingWaitTime(timeSinceDeath);
this.enabled = waitTime == 0;
this.displayString = I18n.format(translationKey + (waitTime == 0 ? "_ready" : "_wait"), waitTime);
super.drawButton(mc, mouseX, mouseY, partialTicks);
}
// Event handlers
@SubscribeEvent
public static void onClientTickEvent(TickEvent.ClientTickEvent event){
if(event.phase == TickEvent.Phase.START && timeSinceDeath >= 0) timeSinceDeath++;
}
@SubscribeEvent
public static void onGuiScreenInitEvent(GuiScreenEvent.InitGuiEvent event){
if(event.getGui() instanceof GuiGameOver && ItemArtefact.isArtefactActive(Minecraft.getMinecraft().player, WizardryItems.amulet_resurrection)
&& WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream().anyMatch(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player))){
event.getButtonList().add(new GuiButtonResurrect(event.getButtonList().size(), event.getGui().width / 2 - 100,
event.getGui().height / 4 + 120, "spell." + Spells.resurrection.getRegistryName() + ".button"));
timeSinceDeath = 0;
}
}
@SubscribeEvent
public static void onGuiScreenActionPerformedEvent(GuiScreenEvent.ActionPerformedEvent event){
if(event.getGui() instanceof GuiGameOver){
ItemStack stack = WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream()
.filter(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player)).findFirst().orElse(null);
if(stack != null){
if(event.getButton() instanceof GuiButtonResurrect && timeSinceDeath >= 0){
// Cast resurrection on the client player and notify the server to do the same
// ISpellCastingItem#canCast already checked in Resurrection#canStackResurrect
((ISpellCastingItem)stack.getItem()).cast(stack, Spells.resurrection, Minecraft.getMinecraft().player, EnumHand.MAIN_HAND, 0, new SpellModifiers());
WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.RESURRECT_BUTTON));
}else if(!Minecraft.getMinecraft().world.getGameRules().getBoolean("keepInventory")){
// Any other button drops the wand (N.B. this should be inside the stack != null check or it'll send
// packets unnecessarily and generate incorrect warnings
WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.CANCEL_RESURRECT));
}
timeSinceDeath = -1;
}
}
}
}
@@ -1,4 +1,4 @@
package electroblob.wizardry.client;
package electroblob.wizardry.client.gui;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
@@ -0,0 +1,121 @@
package electroblob.wizardry.client.gui;
import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Keyboard;
import java.util.Map;
public class GuiSpellBook extends GuiScreen {
private int xSize, ySize;
private Spell spell;
private static final Map<Tier, ResourceLocation> textures = ImmutableMap.of(
Tier.NOVICE, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_novice.png"),
Tier.APPRENTICE, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_apprentice.png"),
Tier.ADVANCED, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_advanced.png"),
Tier.MASTER, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_master.png"));
public GuiSpellBook(Spell spell){
super();
xSize = 288;
ySize = 180;
this.spell = spell;
}
/**
* Draws the screen and all the components in it.
*/
public void drawScreen(int par1, int par2, float par3){
int xPos = this.width / 2 - xSize / 2;
int yPos = this.height / 2 - this.ySize / 2;
EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true;
if(Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
GlStateManager.color(1, 1, 1, 1); // Just in case
// Draws spell illustration on opposite page, underneath the book so it shows through the hole.
Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
DrawingUtils.drawTexturedRect(xPos + 146, yPos + 20, 0, 0, 128, 128, 128, 128);
Minecraft.getMinecraft().renderEngine.bindTexture(textures.get(spell.getTier()));
DrawingUtils.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256);
super.drawScreen(par1, par2, par3);
if(discovered){
this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 15, 0);
this.fontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26, 0x777777);
}else{
this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17,
yPos + 15, 0);
this.mc.standardGalacticFontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26,
0x777777);
}
//this.fontRenderer.drawString("-------------------", xPos + 17, yPos + 35, 0);
if(spell.getTier() == Tier.NOVICE){
// Basic is usually white but this doesn't show up.
this.fontRenderer.drawString("Tier: \u00A77" + Tier.NOVICE.getDisplayName(), xPos + 17, yPos + 45, 0);
}else{
this.fontRenderer.drawString("Tier: " + spell.getTier().getDisplayNameWithFormatting(), xPos + 17, yPos + 45, 0);
}
String element = "Element: " + spell.getElement().getFormattingCode() + spell.getElement().getDisplayName();
if(!discovered) element = "Element: ?";
this.fontRenderer.drawString(element, xPos + 17, yPos + 57, 0);
String manaCost = "Mana Cost: " + spell.getCost();
if(spell.isContinuous) manaCost = "Mana Cost: " + spell.getCost() + "/second";
if(!discovered) manaCost = "Mana Cost: ?";
this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 69, 0);
if(discovered){
this.fontRenderer.drawSplitString(spell.getDescription(), xPos + 17, yPos + 83, 118, 0);
}else{
this.mc.standardGalacticFontRenderer.drawSplitString(
SpellGlyphData.getGlyphDescription(spell, player.world), xPos + 17, yPos + 83, 118, 0);
}
}
public void initGui(){
super.initGui();
Keyboard.enableRepeatEvents(true);
this.buttonList.clear();
this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1));
}
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
@Override
public boolean doesGuiPauseGame(){
return Wizardry.settings.booksPauseGame;
}
}
@@ -0,0 +1,589 @@
package electroblob.wizardry.client.gui;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.client.MixedFontRenderer;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHandSide;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.Map.Entry;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiSpellDisplay {
private static final ResourceLocation INDEX = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud/_index.json");
/** A map which stores all loaded HUD skin objects. This gets wiped on resource pack reload and repopulated with
* mappings as specified by {@code _index.json} (these stack between resource packs). The keys in the map correspond
* to the keys in {@code _index.json}, and are sorted in that order, with skins belonging to resource packs sorted
* from lowest to highest priority. The skins in the base mod will therefore always be first. (It should be noted,
* however, that in the gui itself the skins are always sorted in alphabetical order for some reason.) */
private static final Map<String, Skin> skins = new LinkedHashMap<>(14); // 14 is the number of skins packaged with the mod
private static final Gson gson = new Gson();
/** Width and height of the spell icon (very unlikely to change!) */
private static final int SPELL_ICON_SIZE = 32;
/** Number of ticks the spell switching animation plays for. */
private static final int SPELL_SWITCH_TIME = 4;
/** Scale of the next/previous spell names. */
private static final float SPELL_NAME_SCALE = 0.5f;
/** Opacity of the next/previous spell names, as a fraction. */
private static final float SPELL_NAME_OPACITY = 0.3f;
private static final int HALF_HOTBAR_WIDTH = 97; // Half the width of the hotbar, plus a bit for clearance
private static final int OFFHAND_SLOT_WIDTH = 29; // Width of the offhand slot plus the gap between it and the hotbar
/** Controls the spell switching animation. Positive when switching to the next spell, negative when switching to
* the previous spell. Decremented in magnitude by 1 each tick until it reaches 0 again. */
private static int switchTimer = 0;
/**
* Starts the spell switching animation.
* @param next True to switch to the next spell, false for the previous spell.
*/
public static void playSpellSwitchAnimation(boolean next){
switchTimer = next ? SPELL_SWITCH_TIME : -SPELL_SWITCH_TIME;
}
/** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */
public static Set<String> getSkinKeys(){
return Collections.unmodifiableSet(skins.keySet());
}
/** Returns an unmodifiable view of the loaded spell HUD skins map. */
public static Map<String, Skin> getSkins(){
return Collections.unmodifiableMap(skins);
}
/** Returns the skin that corresponds to the given key. */
public static Skin getSkin(String key){
return skins.get(key);
}
// Normally when extending Gui, you'd have to have an instance to access its methods. However, we're not actually
// using any of them, so this class may as well not bother and just be a static event handler. Neat!
@SubscribeEvent
public static void draw(RenderGameOverlayEvent event){
Minecraft mc = Minecraft.getMinecraft();
EntityPlayer player = mc.player;
if(player.isSpectator()) return; // Spectators shouldn't have the spell HUD!
// If the player has a wand in each hand, only displays for the one in the main hand.
ItemStack wand = player.getHeldItemMainhand();
boolean mainHand = true;
if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))){
wand = player.getHeldItemOffhand();
mainHand = false;
// If the player isn't holding a spellcasting item that shows the HUD, then nothing else needs to be done.
if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))) return;
}
int width = event.getResolution().getScaledWidth();
int height = event.getResolution().getScaledHeight();
boolean flipX = Wizardry.settings.spellHUDPosition.flipX;
boolean flipY = Wizardry.settings.spellHUDPosition.flipY;
if(Wizardry.settings.spellHUDPosition.dynamic){
// ............. | This bit is true if the wand is on the left, false if it is on the right
flipX = flipX == ((mainHand ? player.getPrimaryHand() : player.getPrimaryHand().opposite()) == EnumHandSide.LEFT);
}
Skin skin = skins.get(Wizardry.settings.spellHUDSkin);
if(skin == null){
Wizardry.logger.info("The spell HUD skin '" + Wizardry.settings.spellHUDSkin + "' specified in the config"
+ " did not match any of the loaded skins; using the default skin as a fallback.");
skin = skins.get(Settings.DEFAULT_HUD_SKIN_KEY);
if(skin == null){
Wizardry.logger.warn("The default spell HUD skin is missing! A resource pack must have overridden it"
+ " with an invalid JSON file (default.json), please try again without any resource packs.");
return;
}
}
GlStateManager.pushMatrix();
// 'Origin' of the spell hud (bottom left corner of the actual texture, always in the corner of the screen)
int x = flipX ? width : 0;
int y = flipY ? 0: height;
// The space available to render the spell HUD
float xSpace = (float)(width/2 - HALF_HOTBAR_WIDTH);
if(!player.getHeldItemOffhand().isEmpty()
// Tests whether the offhand slot is rendered on the same side of the hotbar as the spell HUD
&& (player.getPrimaryHand() == EnumHandSide.LEFT) == flipX){
xSpace -= OFFHAND_SLOT_WIDTH;
}
// If the skin is at the bottom and the screen width is too small, scale it to avoid the hotbar and offhand
if(!flipY && skin.getWidth() > xSpace){ // width/2 - 91 - 29 taken from GuiInGame line 547
float scale = xSpace / skin.getWidth();
GlStateManager.scale(scale, scale, 1);
x = MathHelper.ceil(x/scale);
y = MathHelper.ceil(y/scale);
}
Spell spell = WandHelper.getCurrentSpell(wand);
int cooldown = WandHelper.getCurrentCooldown(wand);
int maxCooldown = WandHelper.getCurrentMaxCooldown(wand);
if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){
float animationProgress = Math.signum(switchTimer) * ((SPELL_SWITCH_TIME - Math.abs(switchTimer) +
event.getPartialTicks()) / SPELL_SWITCH_TIME);
String prevSpellName = getFormattedSpellName(WandHelper.getPreviousSpell(wand), player, WandHelper.getPreviousCooldown(wand));
String spellName = getFormattedSpellName(spell, player, cooldown);
String nextSpellName = getFormattedSpellName(WandHelper.getNextSpell(wand), player, WandHelper.getNextCooldown(wand));
skin.drawText(x, y, flipX, flipY, prevSpellName, spellName, nextSpellName, animationProgress);
}else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){
boolean discovered = true;
if(!player.isCreative() && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
}
ResourceLocation icon = discovered ? spell.getIcon() : Spells.none.getIcon();
float progress = 1;
// Doesn't really matter what progress is when in creative, but we might as well avoid the calculation.
if(!player.isCreative() && !spell.isContinuous){
// Subtracted partial tick time to make it smoother
progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + event.getPartialTicks()) / maxCooldown;
}
skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative());
}
GlStateManager.popMatrix();
}
/**
* Gets the name of the given spell, with formatting added according to its cooldown and whether the given player
* has discovered it.
* @param spell The spell to get the name of.
* @param player The player to test for having discovered the given spell.
* @param cooldown The spell's current cooldown.
* @return The spell name, with relevant formatting added, for use with the {@link MixedFontRenderer}.
*/
private static String getFormattedSpellName(Spell spell, EntityPlayer player, int cooldown){
boolean discovered = true;
if(!player.isCreative() && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
}
// Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect
String format = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.getElement().getFormattingCode();
if(!discovered) format = "\u00A79";
String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world);
name = format + name;
if(!discovered) name = "#" + name + "#";
return name;
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntity() == Minecraft.getMinecraft().player){ // Makes sure this only gets called once each tick.
if(switchTimer > 0) switchTimer--;
else if(switchTimer < 0) switchTimer++;
}
}
/** Called from preInit in the main mod class (via the proxies) to initialise the HUD skins, and again on each
* resource reload. */
public static void loadSkins(IResourceManager manager){
try {
List<IResource> indexFiles = manager.getAllResources(INDEX);
skins.clear(); // Wipes the skins map before repopulating it
for(IResource indexFile : indexFiles){
BufferedReader reader = new BufferedReader(new InputStreamReader(indexFile.getInputStream()));
JsonElement je = gson.fromJson(reader, JsonElement.class);
JsonObject json = je.getAsJsonObject();
// Need to iterate over these since we don't know what they're called or how many there are
for(Entry<String, JsonElement> entry : json.entrySet()){
String key = entry.getKey(); // Find out what each element is called, this will be the skins map key
// It's a good idea to use JsonUtils because it produces more helpful error messages (that pack
// makers should understand).
JsonObject skinData = JsonUtils.getJsonObject(json, key);
String[] splitName = ResourceLocation.splitObjectName(JsonUtils.getString(skinData, "texture"));
ResourceLocation texture = new ResourceLocation(splitName[0], "textures/" + splitName[1] + ".png");
splitName = ResourceLocation.splitObjectName(JsonUtils.getString(skinData, "metadata"));
ResourceLocation metadata = new ResourceLocation(splitName[0], "textures/" + splitName[1] + ".json");
// The nice thing about this is it overwrites the existing mapping, and since the index files are in
// ascending order of resource pack priority, this means resource packs can override existing skins
// by specifying one with the same key.
skins.put(key, new Skin(texture, metadata));
}
}
} catch (IOException e){
// If an exception is thrown, chances are the resource pack did not have a spell_hud folder, so nothing
// else needs to be done.
Wizardry.logger.error("Error reading spell HUD skin index file: ", e);
}
}
/**
* Instances of this class represent individual HUD skins, complete with texture and all necessary metadata. This
* class serves to separate the logic behind the spell HUD from its actual rendering.
* All information and processing done within this class relates only to the actual drawing; spells and such like
* must be queried outside of this class and fed into the methods as appropriate.
*
* @author Electroblob
* @since Wizardry 4.2
*/
public static class Skin {
/** The texture file for this skin. */
private final ResourceLocation texture;
/** The display name of the skin in the config menu. */
private String name;
/** The description of the skin shown when its button is hovered over in the config menu. */
private String description;
/** Width of the entire spell HUD. */
private int width;
/** Height of the entire spell HUD. */
private int height;
/** Whether the entire HUD is flipped when on the right-hand side of the screen. If this is false, the HUD will
* still appear on the right-hand side of the screen, but in the same orientation as on the left-hand side. */
private boolean mirrorX;
/** Whether the entire HUD is flipped when at the top of the screen. If this is false, the HUD will
* still appear at the top of the screen, but in the same orientation as at the bottom. */
private boolean mirrorY;
/** Distance of the spell icon from the left edge of the screen (or right edge when flipped). */
private int spellIconInsetX;
/** Distance of the spell icon from the bottom edge of the screen (or top edge when flipped). */
private int spellIconInsetY;
/** Distance of the spell name from the left edge of the screen (or right edge when flipped). */
private int textInsetX;
/** Distance of the spell name from the bottom edge of the screen (or the top edge when flipped). */
private int textInsetY;
/** Horizontal distance between the start of adjacent spell names. */
private int cascadeOffsetX;
/** Vertical distance between the start of adjacent spell names. */
private int cascadeOffsetY;
/** Distance of the cooldown bar from the left edge of the screen (or right edge when flipped). */
private int cooldownBarX;
/** Distance of the cooldown bar from the bottom edge of the screen (or top edge when flipped). */
private int cooldownBarY;
/** Length of the cooldown bar. */
private int cooldownBarLength;
/** Height of the cooldown bar. */
private int cooldownBarHeight;
/** Whether the cooldown bar is flipped horizontally when the HUD is on the right-hand side of the screen. */
private boolean cooldownBarMirrorX;
/** Whether the cooldown bar is flipped vertically when the HUD is at the top of the screen. */
private boolean cooldownBarMirrorY;
/** Whether the cooldown bar progress overlay is shown when the cooldown bar is full (i.e. when progress = 1). */
private boolean showCooldownWhenFull;
private final Minecraft mc;
/** Creates a new skin with the given texture and reads its values from the given metadata json file. */
public Skin(ResourceLocation texture, ResourceLocation metadata){
mc = Minecraft.getMinecraft();
this.texture = texture;
try {
// This time we only want the highest priority file
IResource metadataFile = Minecraft.getMinecraft().getResourceManager().getResource(metadata);
BufferedReader reader = new BufferedReader(new InputStreamReader(metadataFile.getInputStream()));
JsonElement je = gson.fromJson(reader, JsonElement.class);
parseJson(je.getAsJsonObject());
} catch (IOException e){
// If an exception is thrown, chances are the resource pack did not have a spell_hud folder, so nothing
// else needs to be done.
Wizardry.logger.error("Error reading spell HUD skin metadata file: ", e);
}
}
/** Returns the display name of this HUD skin, which is shown in the config GUI. */
public String getName(){
return name;
}
/** Returns the description of this HUD skin, which is shown in its tooltip in the config GUI. */
public String getDescription(){
return description;
}
/** Returns the overall width of this spell HUD skin. */
public int getWidth(){
return width;
}
/** Returns the overall height of this spell HUD skin. */
public int getHeight(){
return height;
}
/** Actually reads the metadata values for this skin from the json file. */
private void parseJson(JsonObject json){
// For now, all the keys must be present for the metadata file to work (the only ones that could reasonably
// have a default anyway are the mirror values).
name = JsonUtils.getString(json, "name");
description = JsonUtils.getString(json, "description");
width = JsonUtils.getInt(json, "width");
if(width > 128) Wizardry.logger.warn("The width of the spell HUD skin " + name + " exceeds 128, this may cause it to render strangely.");
height = JsonUtils.getInt(json, "height");
JsonObject mirror = JsonUtils.getJsonObject(json, "mirror");
mirrorX = JsonUtils.getBoolean(mirror, "x");
mirrorY = JsonUtils.getBoolean(mirror, "y");
JsonObject spellIconInset = JsonUtils.getJsonObject(json, "spell_icon_inset");
spellIconInsetX = JsonUtils.getInt(spellIconInset, "x");
spellIconInsetY = JsonUtils.getInt(spellIconInset, "y");
JsonObject textInset = JsonUtils.getJsonObject(json, "text_inset");
textInsetX = JsonUtils.getInt(textInset, "x");
textInsetY = JsonUtils.getInt(textInset, "y");
JsonObject cascadeOffset = JsonUtils.getJsonObject(json, "spell_cascade_offset");
cascadeOffsetX = JsonUtils.getInt(cascadeOffset, "x");
cascadeOffsetY = JsonUtils.getInt(cascadeOffset, "y");
JsonObject cooldownBar = JsonUtils.getJsonObject(json, "cooldown_bar");
cooldownBarX = JsonUtils.getInt(cooldownBar, "x");
cooldownBarY = JsonUtils.getInt(cooldownBar, "y");
cooldownBarLength = JsonUtils.getInt(cooldownBar, "length");
cooldownBarHeight = JsonUtils.getInt(cooldownBar, "height");
JsonObject cooldownBarMirror = JsonUtils.getJsonObject(cooldownBar, "mirror");
cooldownBarMirrorX = JsonUtils.getBoolean(cooldownBarMirror, "x");
cooldownBarMirrorY = JsonUtils.getBoolean(cooldownBarMirror, "y");
showCooldownWhenFull = JsonUtils.getBoolean(cooldownBar, "show_when_full");
}
// The idea of these methods is that everything in here relates only to the actual drawing of the HUD. In other
// words, all processing of which spells to draw and so on is done outside of here. This means that the config
// GUI can easily display its preview without having a player or wand stack object to query.
/**
* Draws the background layer of this HUD skin at the given position with the given orientations, with the given
* spell icon and cooldown bar progress.
*
* @param x The x-coordinate of the corner of the spell HUD. The bottom left corner <i>of the actual texture</i>
* will always be at this position unless mirrorX/Y is false, so for example if flipX is false and flipY is true,
* this will be the corner of the HUD that is closest to the top left corner of the screen.
* @param y The y-coordinate of the corner of the spell HUD; see above.
* @param flipX Whether to flip the HUD horizontally.
* @param flipY Whether to flip the HUD vertically.
* @param icon A {@code ResourceLocation} corresponding to the icon of the selected spell.
* @param cooldownBarProgress The fraction of the cooldown bar to draw; must be between 0 and 1 (inclusive).
* @param creativeMode True to draw the creative mode HUD, false for the survival mode version.
*/
public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode){
// Moves the origin if the HUD does not mirror; neatens the rest of the code.
if(flipX && !mirrorX) x -= width;
if(flipY && !mirrorY) y += height;
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1);
// Spell illustration - this is now done first so it is behind the HUD texture
mc.renderEngine.bindTexture(icon);
int x1 = flipX && mirrorX ? x - spellIconInsetX - SPELL_ICON_SIZE : x + spellIconInsetX;
// y is upside-down so this is the other way round
int y1 = flipY && mirrorY ? y + spellIconInsetY : y - spellIconInsetY - SPELL_ICON_SIZE;
DrawingUtils.drawTexturedRect(x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE);
// Background of spell hud
mc.renderEngine.bindTexture(texture);
x1 = flipX && mirrorX ? x - width : x;
y1 = flipY && mirrorY ? y : y - height;
// The 128 here is a uv value, not a dimension, and hence is left as a hardcoded number.
// TODO: Since the HUD is wider than it is tall, perhaps the creative mode texture should be in the bottom half instead of the right half?
DrawingUtils.drawTexturedFlippedRect(x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY);
// Cooldown bar
if(!creativeMode && cooldownBarProgress > 0 && (showCooldownWhenFull || cooldownBarProgress < 1)){
int l = (int)(cooldownBarProgress * cooldownBarLength);
x1 = flipX && mirrorX ? x - cooldownBarX - (cooldownBarMirrorX ? l : cooldownBarLength) : x + cooldownBarX;
y1 = flipY && mirrorY ? y + cooldownBarY : y - cooldownBarY - cooldownBarHeight;
int u = cooldownBarX; // This doesn't change, even when cooldownBarMirrorX is true, because it should
int v = height; // always start with the left-hand in the actual texture file
DrawingUtils.drawTexturedFlippedRect(x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY);
}
GlStateManager.popMatrix();
// Blend needs to be left enabled here because otherwise the hotbar becomes opaque
}
/**
* Draws the text layer of this HUD skin at the given position with the given orientations, with the given
* spell name strings.
*
* @param x The x-coordinate of the corner of the spell HUD. The bottom left corner <i>of the actual texture</i>
* will always be at this position, so for example if flipX is false and flipY is true, this will be the corner
* of the HUD that is closest to the top left corner of the screen.
* @param y The y-coordinate of the corner of the spell HUD; see above.
* @param flipX Whether to flip the HUD horizontally.
* @param flipY Whether to flip the HUD vertically.
* @param prevSpellName The name of the previous spell. This string will be drawn <i>directly</i> using the
* {@link MixedFontRenderer}; as such it should be supplied with formatting codes and # characters already
* appended.
* @param spellName The name of the currently selected spell; see above.
* @param nextSpellName The name of the next spell; see above.
* @param animationProgress The progress of the spell switching animation, as a fraction between 0 and 1
* (inclusive). Positive values indicate switching forwards, negative values indicate switching backwards, and
* a value of zero indicates that the spell is not currently being switched.
*/
public void drawText(int x, int y, boolean flipX, boolean flipY, String prevSpellName, String spellName, String nextSpellName, float animationProgress){
// Moves the origin if the HUD does not mirror; neatens the rest of the code.
if(flipX && !mirrorX) x -= width;
if(flipY && !mirrorY) y += height;
FontRenderer font = ClientProxy.mixedFontRenderer; // On this occasion we're client-side so this is OK
// Position of the selected spell name in normal display, also used for interpolation when animating
int x1 = flipX && mirrorX ? x - width : x + textInsetX;
// The text is an odd number of pixels high so we need to subtract an extra 1 when not flipped
int y1 = flipY && mirrorY ? y + textInsetY - font.FONT_HEIGHT/2 + 2 : y - textInsetY - font.FONT_HEIGHT/2 - 1;
int maxWidth = width - textInsetX; // Maximum width of the text
if(animationProgress == 0){ // Normal display
float xPrev = flipX && mirrorX ? x - width : x + textInsetX - (flipY ? -1 : 1) * cascadeOffsetX;
float xNext = flipX && mirrorX ? x - width : x + textInsetX + (flipY ? -1 : 1) * cascadeOffsetX;
// Don't ask me why adding 1 to this makes it look more even, it just does!
float yPrev = y1 - (cascadeOffsetY + 1); // No need to account for flipY because previous is always above.
float yNext = y1 + cascadeOffsetY; // No need to account for flipY because next is always below.
float maxWidthPrev = maxWidth + (flipY ? -1 : 1) * cascadeOffsetX;
float maxWidthNext = maxWidth - (flipY ? -1 : 1) * cascadeOffsetX;
int nextPrevClr = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY);
DrawingUtils.drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, SPELL_NAME_SCALE, nextPrevClr, maxWidthPrev, true, flipX && mirrorX);
DrawingUtils.drawScaledStringToWidth(font, spellName, x1, y1, 1, 0xffffffff, maxWidth, true, flipX && mirrorX);
DrawingUtils.drawScaledStringToWidth(font, nextSpellName, xNext, yNext, SPELL_NAME_SCALE, nextPrevClr, maxWidthNext, true, flipX && mirrorX);
}else{ // Switching spells
boolean reverse = animationProgress < 0;
if(reverse) animationProgress = 1 - Math.abs(animationProgress); // Simplest way of reversing the animation
float xPrev = flipX && mirrorX ? x - width : x + textInsetX - (flipY ? -1 : 1) * cascadeOffsetX * animationProgress;
float xNext = flipX && mirrorX ? x - width : x + textInsetX + (flipY ? -1 : 1) * cascadeOffsetX * (1 - animationProgress);
float yPrev = y1 - (cascadeOffsetY + 1) * animationProgress; // No need to account for flipY because previous is always above.
float yNext = y1 + cascadeOffsetY * (1 - animationProgress); // No need to account for flipY because next is always below.
float maxWidthPrev = maxWidth + (flipY ? -1 : 1) * cascadeOffsetX * animationProgress;
float maxWidthNext = maxWidth - (flipY ? -1 : 1) * cascadeOffsetX * (1 - animationProgress);
float scalePrev = SPELL_NAME_SCALE + (1 - SPELL_NAME_SCALE) * (1 - animationProgress);
float scaleNext = SPELL_NAME_SCALE + (1 - SPELL_NAME_SCALE) * (animationProgress);
int clrPrev = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * (1 - animationProgress));
int clrNext = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * animationProgress);
if(reverse){ // Switching to previous spell
// Only renders the next spell and the current one
DrawingUtils.drawScaledStringToWidth(font, spellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX);
DrawingUtils.drawScaledStringToWidth(font, nextSpellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX);
}else{ // Switching to next spell
// Only renders the previous spell and the current one
DrawingUtils.drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX);
DrawingUtils.drawScaledStringToWidth(font, spellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX);
}
}
}
}
}
@@ -1,21 +1,17 @@
package electroblob.wizardry.client;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
package electroblob.wizardry.client.gui.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.client.config.GuiButtonExt;
import net.minecraftforge.fml.client.config.GuiEditArray;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries;
import net.minecraftforge.fml.client.config.*;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.StringEntry;
import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement;
import net.minecraftforge.fml.common.registry.EntityEntry;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* [NYI] Intended as a way of choosing entities by name from all those currently registered, within the config file, so
* that users don't have to look up the entity IDs. I can't get this to work correctly at the moment.
@@ -0,0 +1,141 @@
package electroblob.wizardry.client.gui.config;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.common.config.ConfigElement;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyCategoryElement;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry;
import net.minecraftforge.fml.client.config.IConfigElement;
import java.util.ArrayList;
import java.util.List;
public class GuiConfigWizardry extends GuiConfig {
public GuiConfigWizardry(GuiScreen parent){
super(parent, getConfigEntries(), Wizardry.MODID, false, false,
Wizardry.NAME + " - " + I18n.format("config." + Wizardry.MODID + ".title.general"));
// this.titleLine2 = "File location: " + Wizardry.config.getConfigFile().getAbsolutePath();
}
private static List<IConfigElement> getConfigEntries(){
List<IConfigElement> configList = new ArrayList<IConfigElement>(1);
configList.add(new DummyCategoryElement("gameplayConfig", "config." + Wizardry.MODID + ".category." + Settings.GAMEPLAY_CATEGORY, GameplayCategory.class));
configList.add(new DummyCategoryElement("worldgenConfig", "config." + Wizardry.MODID + ".category." + Settings.WORLDGEN_CATEGORY, WorldgenCategory.class));
configList.add(new DummyCategoryElement("commandsConfig", "config." + Wizardry.MODID + ".category." + Settings.COMMANDS_CATEGORY, CommandsCategory.class));
configList.add(new DummyCategoryElement("clientConfig", "config." + Wizardry.MODID + ".category." + Settings.CLIENT_CATEGORY, ClientCategory.class));
configList.add(new DummyCategoryElement("spellsConfig", "config." + Wizardry.MODID + ".category." + Settings.SPELLS_CATEGORY, SpellsCategory.class));
configList.add(new DummyCategoryElement("resistancesConfig", "config." + Wizardry.MODID + ".category." + Settings.RESISTANCES_CATEGORY, ResistancesCategory.class));
configList.add(new DummyCategoryElement("compatibilityConfig", "config." + Wizardry.MODID + ".category." + Settings.COMPATIBILITY_CATEGORY, CompatibilityCategory.class));
configList.addAll(new ConfigElement(Wizardry.settings.getConfigCategory(Configuration.CATEGORY_GENERAL)).getChildElements());
return configList;
}
// The reason this system is so convoluted is that it's designed for use with the @Config annotation. The problem is,
// I'm not sure whether that will play well with the load phases. Hmmm...
public static abstract class CategoryBase extends CategoryEntry {
public CategoryBase(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override
protected GuiScreen buildChildScreen(){
String category = this.getCategory();
// This GuiConfig object specifies the configID of the object and as such will force-save when it is closed.
// The parent GuiConfig object's entryList will also be refreshed to reflect the changes.
GuiConfig childScreen = new GuiConfig(this.owningScreen,
(new ConfigElement(Wizardry.settings.getConfigCategory(category))).getChildElements(),
this.owningScreen.modID, category, false, false,
Wizardry.NAME + " - " + I18n.format("config." + Wizardry.MODID + ".title." + category));
childScreen.titleLine2 = I18n.format("config." + Wizardry.MODID + ".subtitle." + category);
return childScreen;
}
protected abstract String getCategory();
}
/** Gameplay category of the config gui. */
public static class GameplayCategory extends CategoryBase {
public GameplayCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.GAMEPLAY_CATEGORY; }
}
/** Worldgen category of the config gui. */
public static class WorldgenCategory extends CategoryBase {
public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; }
}
/** Commands category of the config gui. */
public static class CommandsCategory extends CategoryBase {
public CommandsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.COMMANDS_CATEGORY; }
}
/** Client category of the config gui. */
public static class ClientCategory extends CategoryBase {
public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.CLIENT_CATEGORY; }
}
/** Spells category of the config gui. */
public static class SpellsCategory extends CategoryBase {
public SpellsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.SPELLS_CATEGORY; }
}
/** Resistances category of the config gui. */
public static class ResistancesCategory extends CategoryBase {
public ResistancesCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.RESISTANCES_CATEGORY; }
}
/** Commands category of the config gui. */
public static class CompatibilityCategory extends CategoryBase {
public CompatibilityCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.COMPATIBILITY_CATEGORY; }
}
}
@@ -0,0 +1,119 @@
package electroblob.wizardry.client.gui.config;
import com.google.common.collect.Lists;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.client.gui.GuiSpellDisplay.Skin;
import electroblob.wizardry.registry.Spells;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement;
import javax.annotation.Nullable;
import java.util.Map;
public class GuiSelectHUDSkin extends GuiSelectString {
public GuiSelectHUDSkin(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled){
super(parentScreen, configElement, slotIndex, selectableValues, currentValue, enabled);
}
@Override
public void initGui(){
super.initGui();
setEntryListDimensions();
}
@Override
protected void actionPerformed(GuiButton button){
super.actionPerformed(button);
setEntryListDimensions(); // Stops the entry list from resizing when a button is pressed
}
private void setEntryListDimensions(){
this.entryList.setDimensions(150, height, 43, height-43);
this.entryList.left = 10;
this.entryList.maxEntryWidth = 120;
this.entryList.headerPadding = 5;
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks){
super.drawScreen(mouseX, mouseY, partialTicks);
GlStateManager.disableLighting();
if(this.currentValue instanceof String){
this.drawString(this.fontRenderer, I18n.format("config." + Wizardry.MODID + ".spell_hud_skin.preview"), 170, 44, 0xffffff);
int previewLeft = 170;
int previewRight = width-10;
int previewTop = 60;
int previewBottom = height-43;
this.drawGradientRect(previewLeft, previewTop, previewRight, previewBottom, 0x88000000, 0x88000000);
int previewBorder = 10;
Skin skin = GuiSpellDisplay.getSkin((String)this.currentValue);
float scale = Math.min((previewRight - previewLeft - 2*previewBorder)/(float)skin.getWidth(),
(previewBottom - previewTop - 2*previewBorder)/(float)skin.getHeight());
float x = (previewLeft + previewRight)/2 - (skin.getWidth()*scale)/2;
float y = (previewBottom + previewTop)/2 + (skin.getHeight()*scale)/2;
GlStateManager.pushMatrix();
GlStateManager.scale(scale, scale, scale);
skin.drawBackground((int)(x/scale), (int)(y/scale), false, false,
Spells.magic_missile.getIcon(), 0.6f, false);
skin.drawText((int)(x/scale), (int)(y/scale), false, false,
Spells.none.getDisplayNameWithFormatting(),
Spells.magic_missile.getDisplayNameWithFormatting(),
Spells.none.getDisplayNameWithFormatting(), 0);
GlStateManager.popMatrix();
Skin hovered = getHoveredSkin(mouseX, mouseY);
if(hovered != null){
this.drawToolTip(Lists.newArrayList("\u00A7a" + hovered.getName(), "\u00A7e" + hovered.getDescription()),
mouseX, mouseY);
}
}
GlStateManager.enableLighting();
}
/** Returns the skin corresponding to the list entry being hovered over, or null if there is none. */
@Nullable
private Skin getHoveredSkin(int mouseX, int mouseY){
int index = this.entryList.getSlotIndexFromScreenCoords(mouseX, mouseY);
if(index >= 0 && index <= this.entryList.listEntries.size() && mouseY <= this.entryList.bottom){
Object object = entryList.getListEntry(index).getValue();
if(object instanceof String){
return GuiSpellDisplay.getSkin((String)object);
}
}
return null;
}
@Override // Stops the world being visible behind the GUI when configuring from within a world
public void drawWorldBackground(int tint){
this.drawBackground(tint);
}
}
@@ -0,0 +1,93 @@
package electroblob.wizardry.client.gui.config;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiUtils;
import net.minecraftforge.fml.client.config.IConfigElement;
/**
* Same as {@link net.minecraftforge.fml.client.config.GuiConfigEntries.BooleanEntry}, but instead of simply
* displaying 'true' or 'false', allows the two display strings to be specified in the lang file.
*/
// BooleanEntry's constructors are private, so I had to copy the whole goddamn class to change one method. Thanks Forge.
public class NamedBooleanEntry extends GuiConfigEntries.ButtonEntry {
protected final boolean beforeValue;
protected boolean currentValue;
private static final String DEFAULT_KEY = "config.ebwizardry.generic";
public NamedBooleanEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement){
super(owningScreen, owningEntryList, configElement);
this.beforeValue = Boolean.valueOf(configElement.get().toString());
this.currentValue = beforeValue;
this.btnValue.enabled = enabled();
updateValueButtonText();
}
// This is the only method that's any different
@Override
public void updateValueButtonText(){
String langKey = configElement.getLanguageKey() + "." + currentValue;
this.btnValue.displayString = I18n.format(langKey);
// If the key is unspecified, it defaults to the generic 'Enabled'/'Disabled' keys and adds a red/green colour
if(this.btnValue.displayString.equals(langKey)){
this.btnValue.displayString = I18n.format(DEFAULT_KEY + "." + currentValue);
btnValue.packedFGColour = currentValue ? GuiUtils.getColorCode('a', true) : GuiUtils.getColorCode('c', true);
}
}
// Everything from here down is the same as BooleanEntry
@Override
public void valueButtonPressed(int slotIndex){
if(enabled()) currentValue = !currentValue;
}
@Override
public boolean isDefault(){
return currentValue == Boolean.valueOf(configElement.getDefault().toString());
}
@Override
public void setToDefault(){
if(enabled()){
currentValue = Boolean.valueOf(configElement.getDefault().toString());
updateValueButtonText();
}
}
@Override
public boolean isChanged(){
return currentValue != beforeValue;
}
@Override
public void undoChanges(){
if(enabled()){
currentValue = beforeValue;
updateValueButtonText();
}
}
@Override
public boolean saveConfigElement(){
if(enabled() && isChanged()){
configElement.set(currentValue);
return configElement.requiresMcRestart();
}
return false;
}
@Override
public Boolean getCurrentValue(){
return currentValue;
}
@Override
public Boolean[] getCurrentValues(){
return new Boolean[]{getCurrentValue()};
}
}
@@ -0,0 +1,35 @@
package electroblob.wizardry.client.gui.config;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import net.minecraftforge.client.gui.ForgeGuiFactory.ForgeConfigGui.ModIDEntry;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry;
import net.minecraftforge.fml.client.config.IConfigElement;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* Custom config GUI for spell HUD skin selection; displays a list of all the loaded skins and a preview of the currently
* selected skin. based off of {@link ModIDEntry} from Forge.
*/
public class SpellHUDSkinChooserEntry extends SelectValueEntry {
public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop, getSelectableValues());
if(this.selectableValues.size() == 0) this.btnValue.enabled = false;
}
private static Map<Object, String> getSelectableValues(){
return GuiSpellDisplay.getSkins().entrySet().stream().collect(Collectors.toMap(Entry::getKey,
e -> e.getValue().getName()));
}
@Override // Copied from superclass to use custom child screen GUI class
public void valueButtonPressed(int slotIndex){
mc.displayGuiScreen(new GuiSelectHUDSkin(this.owningScreen, configElement, slotIndex, selectableValues, currentValue, enabled()));
}
}
@@ -0,0 +1,194 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.JsonUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Instances of this class represent tables of contents in the wizard's handbook. Each {@link Section} can have a
* single table of contents, which can reference any other sections in the handbook (though it is normal to list
* top-level sections in a main contents and have subsections listed in their respective parent sections' contents).
*
* This class handles JSON parsing, formatting and drawing of the contents itself, working on a line-by-line basis
* (as opposed to sections, which work on a page-by-page basis). It also stores its own list of buttons.
*
* @author Electroblob
* @since Wizardry 4.2
*/
class Contents {
// Final fields are mandatory, the rest are optional
final String id;
final Section section;
private boolean hyperlinks = true;
private boolean pageNumbers = true;
private String separator = ".";
// Derived fields, not specifically defined in JSON
private int startPage;
private int startLine;
private final List<List<GuiButton>> buttons;
private final List<Section> entries;
private List<Section> visibleEntries;
private Contents(String id, Section section){
this.id = id;
this.section = section;
this.entries = new ArrayList<>();
this.buttons = new ArrayList<>();
this.visibleEntries = new ArrayList<>();
}
/** Returns an unmodifiable, flattened collection of all the buttons in this contents. */
Collection<GuiButton> getButtons(){
return WizardryUtilities.flatten(buttons);
}
void addEntry(Section section){
entries.add(section);
}
/**
* Draws this contents for the given double-page spread and shows/hides buttons accordingly. Will draw nothing
* if the given page is outside of this contents.
*
* @param font The font renderer object.
* @param doublePage The index of the <b>double-page</b> to be drawn.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
*/
void draw(FontRenderer font, int doublePage, int left, int top){
// Show/hide buttons
int i = 0;
for(List<GuiButton> list : buttons){
final int i1 = i++;
list.forEach(b -> b.visible = GuiWizardHandbook.singleToDoublePage(startPage + i1) == doublePage);
}
if(!pageNumbers) return; // No page numbers means only the buttons are drawn
// FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14.
final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT;
int leftIndex = GuiWizardHandbook.doubleToSinglePage(doublePage, false);
// Relative indices of the pages to be rendered - often these will be outside the section entirely
int[] visiblePages = {leftIndex - startPage, leftIndex - startPage + 1};
for(int page : visiblePages){
if(page >= 0 && page < visibleEntries.size() / maxLineNumber + 1){
int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X);
int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT;
for(Section entry : this.visibleEntries){
if(entry.isUnlocked()){
int nameWidth = font.getStringWidth(entry.title);
String dotsAndNumber = " " + entry.startPage;
while(font.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - nameWidth - 2){
dotsAndNumber = separator + dotsAndNumber;
}
font.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - font.getStringWidth(dotsAndNumber), y, DrawingUtils.BLACK, false);
if(!hyperlinks) font.drawString(entry.title, x, y, DrawingUtils.BLACK, false);
y += font.FONT_HEIGHT;
}
}
}
}
}
/**
* Called on GUI load to format the section and all subsections, contents tables and other elements. Does not
* perform any actual drawing.
*
* @param font The font renderer object, for measurement purposes.
* @param startPage The index of the first page (single side, not double-page) of this section.
* @param startLine The index of the first line of this contents.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
* @return The number of lines this contents takes up.
* @throws JsonSyntaxException if at any point the formatting is found to be invalid.
*/
int format(FontRenderer font, int startPage, int startLine, int left, int top){
this.buttons.clear();
this.visibleEntries = new ArrayList<>(entries); // Need to copy the collection first!
this.visibleEntries.removeIf(s -> !s.isUnlocked());
if(hyperlinks){
// FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14.
final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT;
this.startPage = startPage;
this.startLine = startLine;
List<GuiButton> list = new ArrayList<>(maxLineNumber);
for(Section entry : this.visibleEntries){
int x = GuiWizardHandbook.isRightPage(startPage) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X;
int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT;
list.add(new GuiButtonHyperlink.Internal(0, x, y, font, entry.title, entry, 0, "", maxLineNumber-startLine, GuiWizardHandbook.isRightPage(startPage)));
startLine++;
if(startLine == maxLineNumber){
startLine = 0;
startPage++;
buttons.add(list);
list = new ArrayList<>(maxLineNumber); // If there are no more entries this will be discarded anyway
}
}
buttons.add(list);
}
// Returning this is kind of trivial at the moment but if we ever wanted to add a header or something,
// it would be more useful.
return visibleEntries.size();
}
/**
* Parses the given JSON object and constructs a new {@code Contents} from it, setting all the relevant fields
* and references.
*
* @param parent The parent section for this contents.
* @param json A JSON object representing the contents to be constructed. This must contain at least an "id"
* string.
* @return The resulting {@code Contents} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
static Contents fromJson(Section parent, JsonObject json){
Contents contents = new Contents(JsonUtils.getString(json, "id"), parent);
contents.hyperlinks = JsonUtils.getBoolean(json, "hyperlinks", true);
contents.pageNumbers = JsonUtils.getBoolean(json, "page_numbers", true);
contents.separator = JsonUtils.getString(json, "separator", ".");
return contents;
}
}
@@ -0,0 +1,226 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.common.collect.Streams;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.CraftingManager;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import java.util.*;
class CraftingRecipe {
static final int BORDER = 7;
static final int TEXTURE_INSET_X = 40, TEXTURE_INSET_Y = 190;
static final int WIDTH = 121, HEIGHT = 66;
// Final fields are mandatory, the rest are optional
private final ResourceLocation[] locations;
// Derived fields, not specifically defined in JSON
private List<IRecipe> recipes;
private final Set<int[]> instances = new HashSet<>();
private CraftingRecipe(ResourceLocation[] locations){
this.locations = locations;
}
/**
* Adds an instance of this recipe to the list.
*
* @param page The index of the <b>single</b> page this image is on.
* @param x The x-coordinate of the top-left corner of the image, <i>relative</i> to the top-left corner of the GUI.
* @param y The y-coordinate of the top-left corner of the image, <i>relative</i> to the top-left corner of the GUI.
*/
void addInstance(int page, int x, int y){
instances.add(new int[]{page, x, y});
}
/** Removes all instances of this recipe from the list. */
void clearInstances(){
instances.clear();
}
/** Called on GUI open to load the actual recipe object from the registry. This cannot be done on JSON load since
* the recipes aren't necessarily loaded at that point. */
void load(){
recipes = new ArrayList<>(locations.length);
for(ResourceLocation location : locations){
IRecipe recipe = CraftingManager.getRecipe(location);
if(recipe == null) throw new JsonSyntaxException("No such recipe: " + location);
recipes.add(recipe);
}
}
/**
* Draws all instances of this recipe that are located on the given double-page spread.
*
* @param font The font renderer object.
* @param itemRenderer The item renderer object.
* @param doublePage The double-page index of the page to be drawn.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
*/
void draw(FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top){
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], recipes.get(index % recipes.size()));
}
}
}
/**
* Draws the tooltips for all instances of this recipe that are located on the given double-page spread. This has to
* be done separately so that the tooltips are on top of everything else.
*
* @param itemRenderer The item renderer object.
* @param doublePage The double-page index of the page to be drawn.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
*/
void drawTooltips(GuiWizardHandbook gui, FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top, int mouseX, int mouseY){
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, recipes.get(index % recipes.size()));
}
}
}
/**
* Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields
* and references.
*
* @param json A JSON object representing the image to be constructed. This must contain at least a "locations"
* string.
* @return The resulting {@code Image} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
static CraftingRecipe fromJson(JsonObject json){
ResourceLocation[] locations = Streams.stream(JsonUtils.getJsonArray(json, "locations"))
.map(je -> new ResourceLocation(je.getAsString())).toArray(ResourceLocation[]::new);
return new CraftingRecipe(locations);
}
static void populate(Map<String, CraftingRecipe> map, JsonObject json){
JsonObject sectionsObject = JsonUtils.getJsonObject(json, "recipes");
// Need to iterate over these since we don't know what they're called or how many there are
for(Map.Entry<String, JsonElement> entry : sectionsObject.entrySet()){
String key = entry.getKey(); // Find out what each element is called, this will be the sections map key
CraftingRecipe recipe = fromJson(entry.getValue().getAsJsonObject());
map.put(key, recipe);
}
}
private static void renderCraftingRecipe(FontRenderer font, RenderItem itemRenderer, int x, int y, IRecipe recipe){
ItemStack result = recipe.getRecipeOutput();
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
DrawingUtils.drawTexturedRect(x, y, TEXTURE_INSET_X, TEXTURE_INSET_Y, WIDTH, HEIGHT, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT);
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
itemRenderer.zLevel = 100.0F;
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
int i = 0;
for(Ingredient ingredient : recipe.getIngredients()){
if(ingredient != Ingredient.EMPTY){
ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length];
if(!stack.isEmpty()){
itemRenderer.renderItemAndEffectIntoGUI(stack, x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3));
itemRenderer.renderItemOverlays(font, stack, x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3));
}
}
i++;
}
if(!result.isEmpty()){
itemRenderer.renderItemAndEffectIntoGUI(result, x + BORDER + 86, y + BORDER + 18);
itemRenderer.renderItemOverlays(font, result, x + BORDER + 86, y + BORDER + 18);
}
GlStateManager.popMatrix();
GlStateManager.enableDepth();
GlStateManager.disableColorMaterial();
itemRenderer.zLevel = 0.0F;
RenderHelper.disableStandardItemLighting();
}
private static void renderCraftingTooltips(GuiWizardHandbook gui, RenderItem itemRenderer, int x, int y, int mouseX, int mouseY, IRecipe recipe){
ItemStack result = recipe.getRecipeOutput();
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
itemRenderer.zLevel = 0.0F;
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
int i = 0;
for(Ingredient ingredient : recipe.getIngredients()){
if(ingredient != Ingredient.EMPTY){
ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length];
if(!stack.isEmpty() && isPointInRegion(x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3), 16, 16, mouseX, mouseY)){
gui.renderToolTip(stack, mouseX, mouseY);
}
}
i++;
}
if(!result.isEmpty() && isPointInRegion(x + BORDER + 86, y + BORDER + 18, 16, 16, mouseX, mouseY)){
gui.renderToolTip(result, mouseX, mouseY);
}
GlStateManager.popMatrix();
GlStateManager.enableDepth();
GlStateManager.disableColorMaterial();
RenderHelper.disableStandardItemLighting();
}
private static boolean isPointInRegion(int left, int top, int width, int height, int mouseX, int mouseY){
return mouseX >= left - 1 && mouseX < left + width + 1 && mouseY >= top - 1 && mouseY < top + height + 1;
}
}
@@ -0,0 +1,227 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.event.ClickEvent;
import java.util.ArrayList;
import java.util.List;
public abstract class GuiButtonHyperlink extends GuiButton {
public static final String URL_REGEX = "^((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$";
/** Pulse period of links to new sections, in milliseconds. */
private static final float PULSATION_PERIOD = 1500;
final int indent;
final List<String> lines;
final int linesLeft;
GuiButtonHyperlink(int id, int x, int y, FontRenderer font, String text, int indent, String suffix, int linesLeft, boolean rightPage){
super(id, x, y, font.getStringWidth(text), font.FONT_HEIGHT, text);
// Sometimes a link has punctuation or something after it that causes it to wrap onto a new line
String linkWithSuffix = text + suffix;
// If the string won't fit any words at the end of the current line, treat it as if we started a new line
if(font.getStringWidth(linkWithSuffix.split("\\s")[0]) > GuiWizardHandbook.PAGE_WIDTH - indent){
indent = 0;
this.y += font.FONT_HEIGHT;
}
this.indent = indent; // Assigned here in case it was corrected above
this.linesLeft = linesLeft;
String line1 = font.listFormattedStringToWidth(linkWithSuffix, GuiWizardHandbook.PAGE_WIDTH - indent).get(0);
// Without trim(), there will be at least 1 leading space due to the custom wrapping
String remainder = linkWithSuffix.substring(line1.length()).trim();
// ... then wrap the rest to the normal width.
lines = new ArrayList<>();
lines.add(line1);
// Some links are only one line, if this wasn't checked they would cause a StackOverflowError
if(!remainder.isEmpty()) lines.addAll(font.listFormattedStringToWidth(remainder, GuiWizardHandbook.PAGE_WIDTH));
// Removes the suffix if it exists (ugly as heck, but it works)
if(!suffix.isEmpty()){
for(int i=lines.size()-1; i>=0; i--){
String line = lines.get(i);
if(suffix.endsWith(line)){
lines.remove(i);
}else if(line.endsWith(suffix)){
lines.set(i, line.substring(0, line.length() - suffix.length()));
break;
}
}
}
// Remove any lines that overflowed onto the next double-page
if(rightPage){
while(lines.size() > linesLeft) lines.remove(lines.size() - 1);
}
}
public boolean isHovered(net.minecraft.client.gui.FontRenderer font, int mouseX, int mouseY){
int i = 0;
for(String line : lines){
int l = x;
if(i == 0) l += indent;
int t = y + font.FONT_HEIGHT * i;
if(i > linesLeft){
l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH;
t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % font.FONT_HEIGHT);
}
if(mouseX >= l && mouseY >= t && mouseX < l + font.getStringWidth(line) && mouseY < t + font.FONT_HEIGHT){
return true;
}
i++;
}
return false;
}
@Override
public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){
return this.enabled && this.visible && isHovered(minecraft.fontRenderer, mouseX, mouseY);
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
if(this.visible){
this.hovered = isHovered(minecraft.fontRenderer, mouseX, mouseY);
int i = 0;
for(String line : lines){
int l = x;
if(i == 0) l += indent;
int t = y + minecraft.fontRenderer.FONT_HEIGHT * i;
if(i > linesLeft){
l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH;
t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % minecraft.fontRenderer.FONT_HEIGHT);
}
minecraft.fontRenderer.drawString(line, l, t, getColour());
i++;
}
}
}
protected int getColour(){
return hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink");
}
/**
* Creates a new hyperlink button from the given arguments, automatically differentiating between URLs and sections.
* @param x The x position of the button
* @param y The y position of the button
* @param font A reference to the FontRenderer object
* @param upToLink The paragraph (as a list of lines) up to the link, used to determine positioning and word wrap
* @param arguments The link arguments - that is, everything between the two @ signs, split by spaces
* @param suffix The text directly after the link, up to the first whitespace; used for word wrap. Usually this is
* either empty or contains a single punctuation mark.
* @return The resulting button
* @throws IllegalArgumentException if the given argument array is empty or contains more than 2 arguments
* @throws JsonSyntaxException if the specified link target is not a URL or a valid section ID
*/
public static GuiButtonHyperlink create(int x, int y, FontRenderer font, List<String> upToLink, String[] arguments, String suffix, int linesLeft, boolean rightPage){
if(arguments.length == 0 || arguments.length > 2) throw new IllegalArgumentException("Incorrect array length!");
GuiButtonHyperlink button;
if(arguments[0].matches(URL_REGEX)){
button = new GuiButtonHyperlink.External(0, x, y, font, arguments[arguments.length - 1], arguments[0],
font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage);
}else{
Section target = GuiWizardHandbook.sections.get(arguments[0]);
if(target == null) throw new JsonSyntaxException("Hyperlink points to nonexistent section id " + arguments[0]);
button = new GuiButtonHyperlink.Internal(0, x, y, font, arguments[arguments.length - 1],
target, font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage);
}
return button;
}
static class Internal extends GuiButtonHyperlink {
final Section target;
Internal(int id, int x, int y, FontRenderer font, String text, Section target, int indent, String suffix, int linesLeft, boolean rightPage){
super(id, x, y, font, text, indent, suffix, linesLeft, rightPage);
this.target = target;
}
@Override
public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){
if(!target.isUnlocked()) return false;
return super.mousePressed(minecraft, mouseX, mouseY);
}
@Override
public void playPressSound(SoundHandler soundHandler){
soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1));
}
@Override
protected int getColour(){
if(!target.isUnlocked()) return GuiWizardHandbook.colours.get("text");
if(!hovered && target.isNew() && !Minecraft.getMinecraft().player.isCreative()){
int c = GuiWizardHandbook.colours.get("new_section");
int d = GuiWizardHandbook.colours.get("hyperlink");
float f = (MathHelper.sin((Minecraft.getSystemTime() % PULSATION_PERIOD) / PULSATION_PERIOD * 2 * (float)Math.PI) + 1) / 2f;
return DrawingUtils.mix(c, d, f);
}
return super.getColour();
}
}
static class External extends GuiButtonHyperlink {
final ITextComponent link;
External(int id, int x, int y, FontRenderer font, String text, String url, int indent, String suffix, int linesLeft, boolean rightPage){
super(id, x, y, font, text, indent, suffix, linesLeft, rightPage);
this.link = new TextComponentString(text);
link.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)).setColor(TextFormatting.DARK_BLUE);
}
}
}
@@ -0,0 +1,61 @@
package electroblob.wizardry.client.gui.handbook;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.ResourceLocation;
//@SideOnly(Side.CLIENT)
class GuiButtonTurnPage extends GuiButton {
static final int WIDTH = 20;
static final int HEIGHT = 12;
enum Type {
NEXT_PAGE(0, 196),
PREVIOUS_PAGE(0, 208),
NEXT_SECTION(0, 220),
PREVIOUS_SECTION(0, 232),
CONTENTS(0, 244);
private final int u, v;
Type(int u, int v){
this.u = u;
this.v = v;
}
}
public final Type type;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png");
public GuiButtonTurnPage(int id, int x, int y, Type type){
super(id, x, y, WIDTH, HEIGHT, "");
this.type = type;
}
@Override
public void playPressSound(SoundHandler soundHandler){
soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1));
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
if(this.visible){
boolean flag = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
minecraft.getTextureManager().bindTexture(texture);
DrawingUtils.drawTexturedRect(this.x, this.y, flag ? type.u + width : type.u, type.v, width, height, 512, 256);
}
}
}
@@ -0,0 +1,564 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.client.gui.GuiButtonInvisible;
import electroblob.wizardry.client.gui.handbook.GuiButtonTurnPage.Type;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.packet.PacketRequestAdvancementSync;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.item.ItemStack;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Keyboard;
import java.awt.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.*;
/**
* GUI class for the wizard's handbook. Like any GUI class, this is instantiated each time the book is opened. As of
* Wizardry 4.2, the handbook text is defined as a JSON file rather than a plain text file, and is loaded only on
* resource pack reload, rather than every time the book is opened. This means all the data structures (sections, images,
* etc.) are built before the GUI instance exists at all. However, since some things depend on positioning, these have to
* be initialised on GUI creation. (Previously, everything was done on GUI load)
*
* @author Electroblob
* @since Wizardry 1.0
* @see Section
* @see Contents
* @see Image
* @see CraftingRecipe
*/
public class GuiWizardHandbook extends GuiScreen {
private static final ResourceLocation DEFAULT = new ResourceLocation(Wizardry.MODID, "texts/handbook_en_us.json");
static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png");
/** Global Gson instance for the handbook. */
private static final Gson gson = new Gson();
// Formatting markup
static final char FORMAT_MARKER = '#';
static final char HYPERLINK_MARKER = '@';
static final String IMAGE_TAG = "image";
static final String RECIPE_TAG = "recipe";
static final String RULER_TAG = "ruler";
static final Map<String, String> FORMAT_TAGS = new HashMap<>();
// Dimension constants
// Private constants are not relevant to book elements, package-protected ones are
/** The dimensions of the rendered GUI area. */
static final int GUI_WIDTH = 288, GUI_HEIGHT = 180;
/** The dimensions of the GUI texture itself. */
static final int TEXTURE_WIDTH = 512, TEXTURE_HEIGHT = 256;
/** The dimensions of the area of a single page in which text can be drawn. */
static final int PAGE_WIDTH = 120, PAGE_HEIGHT = 140;
/** The distance of the text from the top outside corner of each page. */
static final int TEXT_INSET_X = 17, TEXT_INSET_Y = 16;
/** The distance of the buttons from the bottom outside corners of the GUI. */
private static final int BUTTON_INSET_X = 22, BUTTON_INSET_Y = 13;
/** The distance between adjacent buttons. */
private static final int BUTTON_SPACING = 20;
/** The distance of the page numbers from the bottom of the GUI. */
private static final int PAGE_NUMBER_INSET = 22;
// IDEA: Constant dimensions could be converted to JSON like the spell HUD ones
// Global variables
/**
* The <b>double-page</b> currently being viewed. Each double-page spread counts as a single page, with the inside
* of the front cover being page 0.
*/
private int currentPage = 0;
/**
* The number of <b>single</b> pages currently in the book. This is calculated on GUI load based on visible sections.
*/
private int pageCount = 1; // Starts at 1 because the first single-page is the inside of the cover
/**
* The <b>double-page</b> number where the bookmark is currently set, <b>relative to the section stored in
* {@link GuiWizardHandbook#bookmarkSection}</b>. Static because it persists when the book is closed.
*/
private static int bookmarkPage = 0;
/**
* The key corresponding to the section in which the bookmark is currently set. Static because it persists when the
* book is closed. Storing a section means the bookmark doesn't change location when new sections are unlocked.
*/
private static String bookmarkSection;
// Buttons
private GuiButton bookmark, next, previous, nextSection, previousSection, menu;
// Handbook content
// As a general rule, I prefer to make static final fields lowercase if they're collections that change, because even
// though the collection itself is constant, the stuff in it is not, so being lowercase highlights this difference.
/**
* A map which stores all loaded section objects, including subsections. This gets wiped on resource pack reload and
* repopulated with mappings as specified by the handbook JSON file for the current language. The keys in the map
* correspond to the keys in the sections object in that file, and are sorted in that order.
*/
static final Map<String, Section> sections = new LinkedHashMap<>();
/**
* A list which stores all loaded section objects, including subsections. This is an unmodifiable list view of the
* values in {@link GuiWizardHandbook#sections}, sorted in the same (page number) order. This exists only to allow
* sections to be accessed by ordinal index for the various navigation buttons, hence why it is private.
*/
private static List<Section> sectionList;
/**
* A map which stores all loaded contents objects. This gets wiped on resource pack reload and repopulated with
* mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the
* id strings for the contents objects in that file. This map is not sorted.
*/
static final Map<String, Contents> contentsList = new HashMap<>();
/**
* A map which stores all loaded hex colour values. This gets wiped on resource pack reload and repopulated with
* mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the
* keys in the colours object in that file. This map is not sorted.
*/
static final Map<String, Integer> colours = new HashMap<>();
/**
* A map which stores all loaded image objects. This gets wiped on resource pack reload and repopulated with
* mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the
* keys in the images object in that file. This map is not sorted.
*/
static final Map<String, Image> images = new HashMap<>();
/**
* A map which stores all loaded crafting recipe objects. This gets wiped on resource pack reload and repopulated
* with mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to
* the keys in the recipes object in that file. This map is not sorted.
*/
static final Map<String, CraftingRecipe> recipes = new HashMap<>();
/**
* Adds a format tag to the handbook. All occurrences of the given tag string preceded by a # will be replaced with
* the result of the given value string on GUI load. The value string, therefore, can be anything that should be
* input dynamically, as long as it does not change while the GUI is open. Examples include wizardry's version,
* the various element colours and the keys assigned to wizardry's controls.
* @param tag The tag string, as defined in the handbook JSON file, excluding the # character. Cannot include spaces.
* @param value The string to replace occurrences of the given format tag with. Can include spaces but not the # character.
*/
public static void addFormatTag(String tag, String value){
FORMAT_TAGS.put(tag, value);
}
private static void initFormatTags(){
addFormatTag("next_spell_key", ClientProxy.NEXT_SPELL.getDisplayName());
addFormatTag("previous_spell_key", ClientProxy.PREVIOUS_SPELL.getDisplayName());
addFormatTag("example_charging_loss", "" + (Constants.MANA_PER_CRYSTAL - 30));
addFormatTag("mana_per_crystal", "" + Constants.MANA_PER_CRYSTAL);
addFormatTag("novice_max_charge", "" + Tier.NOVICE.maxCharge);
addFormatTag("apprentice_max_charge", "" + Tier.APPRENTICE.maxCharge);
addFormatTag("advanced_max_charge", "" + Tier.ADVANCED.maxCharge);
addFormatTag("master_max_charge", "" + Tier.MASTER.maxCharge);
addFormatTag("version", Wizardry.VERSION);
addFormatTag("mcversion", Minecraft.getMinecraft().getVersion());
addFormatTag("colour_novice", "\u00A77");
addFormatTag("colour_apprentice", Tier.APPRENTICE.getFormattingCode());
addFormatTag("colour_advanced", Tier.ADVANCED.getFormattingCode());
addFormatTag("colour_master", Tier.MASTER.getFormattingCode());
addFormatTag("colour_fire", Element.FIRE.getFormattingCode());
addFormatTag("colour_ice", Element.ICE.getFormattingCode());
addFormatTag("colour_lightning", Element.LIGHTNING.getFormattingCode());
addFormatTag("colour_necromancy", Element.NECROMANCY.getFormattingCode());
addFormatTag("colour_earth", Element.EARTH.getFormattingCode());
addFormatTag("colour_sorcery", Element.SORCERY.getFormattingCode());
addFormatTag("colour_healing", Element.HEALING.getFormattingCode());
addFormatTag("colour_reset", "\u00A70");
}
// Helper methods
/**
* Converts the given single page index to a double-page index. Inverse of
* {@link GuiWizardHandbook#doubleToSinglePage(int, boolean)}.
*
* @param singlePageIndex The single-page index, which is the same as the page numbers actually displayed.
* @return The corresponding double-page index.
*/
static int singleToDoublePage(int singlePageIndex){
// Yes, this is trivial, but if I ever change the numbering it'll be useful. It's also more descriptive.
return singlePageIndex / 2;
}
/**
* Converts the given double-page index to a single-page index. Inverse of
* {@link GuiWizardHandbook#singleToDoublePage(int)}.
*
* @param doublePageIndex The double-page index, as stored in {@link GuiWizardHandbook#currentPage}.
* @param rightHandPage True to return the page on the right (1 greater), false for the left-hand page.
* @return The corresponding single-page index.
*/
static int doubleToSinglePage(int doublePageIndex, boolean rightHandPage){
return rightHandPage ? doublePageIndex * 2 + 1 : doublePageIndex * 2;
}
/**
* Returns whether the given page index refers to a right-hand page or a left-hand page.
*
* @param page The single-page index, which is the same as the page number actually displayed.
* @return True if the given page index refers to a right-hand page, false if it is a left-hand page.
*/
static boolean isRightPage(int page){
return page % 2 == 1;
}
// Drawing
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks){
int left = this.width / 2 - GUI_WIDTH / 2;
int top = this.height / 2 - GUI_HEIGHT / 2;
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
GlStateManager.color(1, 1, 1, 1);
// Main background
DrawingUtils.drawTexturedRect(left, top, 0, 0, GUI_WIDTH, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// First page background
if(currentPage == 0){
DrawingUtils.drawTexturedRect(left, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
previous.visible = false;
previousSection.visible = false; // Not worth testing if we're in the first section every frame
menu.visible = false;
}else{
previous.visible = true;
previousSection.visible = true;
menu.visible = true;
}
// Last page background
if(currentPage == singleToDoublePage(pageCount)){
DrawingUtils.drawTexturedFlippedRect(left + GUI_WIDTH / 2, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT, true, false);
next.visible = false;
nextSection.visible = false;
}else{
next.visible = true;
nextSection.visible = true;
}
// Page numbers
if(currentPage > 0){
String pageNumber = "" + doubleToSinglePage(currentPage, false);
this.fontRenderer.drawString(pageNumber, left + TEXT_INSET_X + PAGE_WIDTH / 2
- fontRenderer.getStringWidth(pageNumber)/2, top + GUI_HEIGHT - PAGE_NUMBER_INSET, DrawingUtils.BLACK);
}
if(currentPage < singleToDoublePage(pageCount)){
String pageNumber = "" + doubleToSinglePage(currentPage, true);
this.fontRenderer.drawString(pageNumber, left + GUI_WIDTH - TEXT_INSET_X - PAGE_WIDTH / 2
- fontRenderer.getStringWidth(pageNumber)/2, top + GUI_HEIGHT - PAGE_NUMBER_INSET, DrawingUtils.BLACK);
}
// Main content
contentsList.values().forEach(c -> { if(c.section.isUnlocked()) c.draw(fontRenderer, currentPage, left, top); } );
sections.values().forEach(s -> { if(s.isUnlocked()) s.draw(fontRenderer, currentPage, left, top); } );
// These only get populated if the sections are unlocked so no checks are necessary
images.values().forEach(i -> i.draw(fontRenderer, currentPage, left, top));
recipes.values().forEach(r -> r.draw(fontRenderer, itemRender, currentPage, left, top));
// Buttons
super.drawScreen(mouseX, mouseY, partialTicks);
// Bookmark
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
if(currentPage == singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage){
// If the current page is the bookmarked page, the (invisible) bookmark button is disabled
bookmark.visible = false;
DrawingUtils.drawTexturedRect(left + 138, top, 299, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}else{
bookmark.visible = true;
bookmark.x = left + (currentPage > singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage ? 130 : 147);
DrawingUtils.drawTexturedRect(bookmark.x, top,
bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
// Recipe tooltips
recipes.values().forEach(r -> r.drawTooltips(this, fontRenderer, itemRender, currentPage, left, top, mouseX, mouseY));
}
// GUI Initialisation / Close
@Override
public void onResize(Minecraft minecraft, int width, int height){
initGui();
}
@Override
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
@Override
public void initGui(){
super.initGui();
Keyboard.enableRepeatEvents(true);
initFormatTags();
final int left = this.width / 2 - GUI_WIDTH / 2;
final int top = this.height / 2 - GUI_HEIGHT / 2;
recipes.values().forEach(CraftingRecipe::load);
int nextButtonId = 0;
this.buttonList.clear();
this.buttonList.add(next = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH - BUTTON_INSET_X - GuiButtonTurnPage.WIDTH,
top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_PAGE));
this.buttonList.add(previous = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X,
top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE));
this.buttonList.add(nextSection = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH - BUTTON_INSET_X - GuiButtonTurnPage.WIDTH - BUTTON_SPACING,
top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_SECTION));
this.buttonList.add(previousSection = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X + BUTTON_SPACING,
top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION));
this.buttonList.add(menu = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH/2 - 28,
top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS));
this.buttonList.add(bookmark = new GuiButtonInvisible(nextButtonId++, left + 130, top + 172, 11, 19) {
@Override
public void playPressSound(SoundHandler soundHandler){
soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1));
}
});
pageCount = 1;
// Clears instances of all images and recipes
images.values().forEach(Image::clearInstances);
recipes.values().forEach(CraftingRecipe::clearInstances);
// Formats all the unlocked sections in order
for(Section section : sections.values()){
if(section.isUnlocked()){
pageCount = section.format(this.fontRenderer, pageCount, left, top);
buttonList.addAll(section.getButtons());
}
}
contentsList.values().forEach(c -> buttonList.addAll(c.getButtons()));
this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1));
}
// JSON Parsing / Data Construction
/**
* Called from preInit in the main mod class (via the proxies) to initialise the handbook (parses the JSON file
* and constructs the relevant data structures), and again on each resource reload (changing the language triggers
* a resource reload).
*/
public static void loadHandbookFile(IResourceManager manager){
IResource handbookFile = getHandbookResource(manager);
if(handbookFile != null){
// Wipes all the maps before repopulating them
images.clear();
sections.clear();
contentsList.clear();
colours.clear();
bookmarkSection = null; // Also need to wipe the reference to the old bookmarked section
BufferedReader reader = new BufferedReader(new InputStreamReader(handbookFile.getInputStream()));
JsonElement je = gson.fromJson(reader, JsonElement.class);
JsonObject json = je.getAsJsonObject();
JsonUtils.getJsonObject(json, "colours").entrySet().forEach(e -> colours.put(e.getKey(),
Color.decode(e.getValue().getAsString()).getRGB()));
// Repopulates the remaining maps
Image.populate(images, json);
CraftingRecipe.populate(recipes, json);
Section.populate(sections, json);
sectionList = Collections.unmodifiableList(new ArrayList<>(sections.values()));
if(sections.isEmpty()){
Wizardry.logger.warn("Handbook has no sections! Aborting loading...");
return;
}
bookmarkSection = JsonUtils.getString(json, "bookmark_start_section");
if(!sections.containsKey(bookmarkSection)) throw new JsonSyntaxException("Section with id " + bookmarkSection + " is undefined");
}
// The first resource load on startup is done before the packet handler is loaded
if(WizardryPacketHandler.net != null) WizardryPacketHandler.net.sendToServer(new PacketRequestAdvancementSync.Message());
}
/**
* Retrieves the handbook JSON file for the current language and returns its IResource object. If a handbook file
* cannot be found for the current language, a message is printed to the console and the method attempts to retrieve
* the default file instead (English-US). If this file cannot be found, the resulting error is printed to the
* console and the method returns null.
*
* @param manager The resource manager instance to use.
* @return The handbook JSON file, as an IResource, or null if it was not found.
*/
private static IResource getHandbookResource(IResourceManager manager){
// TODO: Implement resource pack stacking to allow addon mods and texture packs to add/overwrite content
IResource handbookFile = null;
try{
handbookFile = manager.getResource(new ResourceLocation(Wizardry.MODID, "texts/handbook_"
+ Minecraft.getMinecraft().getLanguageManager().getCurrentLanguage().getLanguageCode() + ".json"));
}catch(IOException e){
Wizardry.logger.info("Wizard handbook JSON file missing for the current language (" + Minecraft.getMinecraft()
.getLanguageManager().getCurrentLanguage() + "). Using default (English-US) instead.");
try{
handbookFile = manager.getResource(DEFAULT);
}catch(IOException x){
Wizardry.logger.error("Couldn't find file: " + DEFAULT + ". The file may be missing; please try re-downloading and reinstalling Wizardry.", x);
}
}
return handbookFile;
}
// Controls
@Override
protected void actionPerformed(GuiButton button){
if(button.enabled){
if(button == next){
if(currentPage < singleToDoublePage(pageCount)) currentPage++;
}else if(button == previous){
if(currentPage > 0) currentPage--;
}else if(button == nextSection || button == previousSection){
Section currentSection = null;
for(Section section : sections.values()){
// We always want this button to do something, and taking the right-hand page means it always does
if(section.containsPage(doubleToSinglePage(currentPage, true))){
currentSection = section;
break;
}
}
if(currentSection != null){
List<Section> visibleSections = new ArrayList<>(sectionList);
visibleSections.removeIf(s -> !s.isUnlocked());
int index = visibleSections.indexOf(currentSection);
if(button == nextSection && index + 1 < visibleSections.size()){
currentPage = singleToDoublePage(visibleSections.get(index + 1).startPage);
}else if(index > 0){
currentPage = singleToDoublePage(visibleSections.get(index - 1).startPage);
}
}
}else if(button == menu){
currentPage = singleToDoublePage(sections.get("main_contents").startPage);
}else if(button == bookmark && bookmarkSection != null){
currentPage = singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage;
}else{
if(button instanceof GuiButtonHyperlink.Internal){
currentPage = singleToDoublePage(((GuiButtonHyperlink.Internal)button).target.startPage);
}else if(button instanceof GuiButtonHyperlink.External){
this.handleComponentClick(((GuiButtonHyperlink.External)button).link);
}
}
}
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException{
if(mouseButton == 1){
// Right-clicking of bookmark
if(bookmark.mousePressed(this.mc, mouseX, mouseY)){
this.selectedButton = bookmark;
for(String key : sections.keySet()){
// The bookmark is assumed to bookmark the left-hand page
if(sections.get(key).containsPage(doubleToSinglePage(currentPage, false))) bookmarkSection = key;
}
bookmarkPage = currentPage - singleToDoublePage(sections.get(bookmarkSection).startPage);
}
}else{
super.mouseClicked(mouseX, mouseY, mouseButton);
}
}
// Overridden to make it public
@Override
public void renderToolTip(ItemStack stack, int x, int y){
super.renderToolTip(stack, x, y);
}
@Override
public boolean doesGuiPauseGame(){
return Wizardry.settings.booksPauseGame;
}
public static void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){
sections.values().forEach(s -> s.updateUnlockStatus(showToasts, completedAdvancements));
}
}
@@ -0,0 +1,54 @@
package electroblob.wizardry.client.gui.handbook;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.client.gui.toasts.GuiToast;
import net.minecraft.client.gui.toasts.IToast;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import java.util.List;
//@SideOnly(Side.CLIENT)
public class HandbookToast implements IToast {
private final Section section;
public HandbookToast(Section section){
this.section = section;
}
public IToast.Visibility draw(GuiToast toastGui, long delta){
toastGui.getMinecraft().getTextureManager().bindTexture(TEXTURE_TOASTS);
GlStateManager.color(1.0F, 1.0F, 1.0F);
toastGui.drawTexturedModalRect(0, 0, 0, 32, 160, 32);
boolean firstPart = delta < 1500L;
int a = firstPart ? MathHelper.floor(MathHelper.clamp((float)(1500L - delta) / 300.0F, 0.0F, 1.0F) * 255.0F) << 24 | 67108864
: MathHelper.floor(MathHelper.clamp((float)(delta - 1500L) / 300.0F, 0.0F, 1.0F) * 252.0F) << 24 | 67108864;
String s = firstPart ? I18n.format("handbook.toast.title") : section.title;
int c = firstPart ? -11534256 : -16777216;
List<String> list = toastGui.getMinecraft().fontRenderer.listFormattedStringToWidth(s, 125);
int h = 16 - list.size() * toastGui.getMinecraft().fontRenderer.FONT_HEIGHT / 2;
for(String line : list){
toastGui.getMinecraft().fontRenderer.drawString(line, 30, h, c | a);
h += toastGui.getMinecraft().fontRenderer.FONT_HEIGHT;
}
RenderHelper.enableGUIStandardItemLighting();
toastGui.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI(null, new ItemStack(WizardryItems.wizard_handbook), 8, 8);
return delta >= 5000L ? IToast.Visibility.HIDE : IToast.Visibility.SHOW;
}
}
@@ -0,0 +1,148 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
class Image {
// Final fields are mandatory, the rest are optional
private final ResourceLocation location;
private final int width, height;
private int textureWidth, textureHeight;
private int u = 0, v = 0;
private String caption = "";
private boolean border = true;
// Derived fields, not specifically defined in JSON
private final Set<int[]> instances = new HashSet<>();
private static final int CAPTION_OFFSET = 4;
private static final int TEXTURE_INSET_X = 180;
private static final int BORDER = 1;
private Image(ResourceLocation location, int width, int height){
this.location = location;
this.width = width;
this.height = height;
}
/** Returns the width of the image. */
int getWidth(){
return width;
}
/** Returns the total height of the image, including caption if it has one. */
int getHeight(FontRenderer font){
return caption.isEmpty() ? height : height + CAPTION_OFFSET + font.FONT_HEIGHT;
}
/**
* Adds an instance of this image to the list.
*
* @param page The index of the <b>single</b> page this image is on.
* @param x The x-coordinate of the top-left corner of the image, <i>relative</i> to the top-left corner of the GUI.
* @param y The y-coordinate of the top-left corner of the image, <i>relative</i> to the top-left corner of the GUI.
*/
void addInstance(int page, int x, int y){
instances.add(new int[]{page, x, y});
}
/** Removes all instances of this image from the list. */
void clearInstances(){
instances.clear();
}
/**
* Draws all instances of this image that are located on the given double-page spread.
*
* @param font The font renderer object.
* @param doublePage The double-page index of the page to be drawn.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
*/
void draw(FontRenderer font, int doublePage, int left, int top){
// Images
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
Minecraft.getMinecraft().renderEngine.bindTexture(location);
GlStateManager.color(1, 1, 1, 1);
DrawingUtils.drawTexturedRect(left + instance[1], top + instance[2], u, v, width, height, textureWidth, textureHeight);
font.drawString("\u00A7o" + caption, left + instance[1] + width/ 2 - font.getStringWidth(caption)/2,
top + instance[2] + height + CAPTION_OFFSET, GuiWizardHandbook.colours.get("caption"));
}
}
if(border){
// Borders - do this after all the images are drawn so we only have to bind the handbook texture again once
Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
GlStateManager.color(1, 1, 1, 1);
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
// Math.ceil accounts for odd-numbered image dimensions
DrawingUtils.drawTexturedFlippedRect(left + instance[1] - BORDER, top + instance[2] - BORDER,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, width / 2 + BORDER, height / 2 + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, false, false);
DrawingUtils.drawTexturedFlippedRect(left + instance[1] + width / 2, top + instance[2] - BORDER,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, MathHelper.ceil(width / 2f) + BORDER, height / 2 + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, true, false);
DrawingUtils.drawTexturedFlippedRect(left + instance[1] - BORDER, top + instance[2] + height / 2,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, width / 2 + BORDER, MathHelper.ceil(height / 2f) + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, false, true);
DrawingUtils.drawTexturedFlippedRect(left + instance[1] + width / 2, top + instance[2] + height / 2,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, MathHelper.ceil(width / 2f) + BORDER, MathHelper.ceil(height / 2f) + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, true, true);
}
}
}
}
/**
* Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields
* and references.
*
* @param json A JSON object representing the image to be constructed. This must contain at least a "location"
* string.
* @return The resulting {@code Image} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
static Image fromJson(JsonObject json){
Image image = new Image(new ResourceLocation(JsonUtils.getString(json, "location")),
JsonUtils.getInt(json, "width"), JsonUtils.getInt(json, "height"));
image.u = JsonUtils.getInt(json, "u", 0);
image.v = JsonUtils.getInt(json, "v", 0);
image.textureWidth = JsonUtils.getInt(json, "texture_width", image.width);
image.textureHeight = JsonUtils.getInt(json, "texture_height", image.height);
image.caption = JsonUtils.getString(json, "caption", "");
image.border = JsonUtils.getBoolean(json, "border", true);
return image;
}
static void populate(Map<String, Image> map, JsonObject json){
JsonObject sectionsObject = JsonUtils.getJsonObject(json, "images");
// Need to iterate over these since we don't know what they're called or how many there are
for(Map.Entry<String, JsonElement> entry : sectionsObject.entrySet()){
String key = entry.getKey(); // Find out what each element is called, this will be the sections map key
Image image = fromJson(entry.getValue().getAsJsonObject());
map.put(key, image);
}
}
}
@@ -0,0 +1,451 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.common.collect.Streams;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/**
* Instances of this class represent sections in the wizard's handbook. As of wizardry 4.2, this class handles
* everything within the section itself, including JSON parsing, unlock triggers and drawing the actual rawText.
* Sections may now also be nested and have other elements within them, such as images and a table of contents, a
* behaviour which is also handled within this class.
* <p></p>
* The formatting of the book is now done 'dynamically' - that is, the exact positions and page numbers of
* sections, images and so on are determined on GUI load and depend on which of the previous sections have been
* unlocked, amongst other factors. This means that all of the unlocked sections must be formatted in order on GUI
* load, so that each section knows the previous section's length and therefore where to start.
*
* @author Electroblob
* @since Wizardry 4.2
*/
// Because these are now generated on resource pack reload (not on handbook open, as before), this class can no longer
// be a non-static inner class
class Section {
// Final fields are mandatory (none here though), the rest are optional
String title;
private String[] rawText;
private Contents contents;
private ResourceLocation[] triggers;
private Map<String, Section> subsections;
private boolean centreX, centreY;
// Derived fields, not explicitly defined in JSON
/** The <b>single-page</b> index of the first page of this section. */
int startPage;
private final List<List<GuiButton>> buttons;
/**
* A list of <b>single</b> pages, which are themselves lists of paragraphs (each paragraph is a single
* string which may include line breaks and other escape characters).
*/
private final List<List<String>> pages;
private boolean unlocked = false;
private boolean isNew = false;
private Section(){
this.buttons = new ArrayList<>();
this.pages = new ArrayList<>();
this.subsections = new LinkedHashMap<>();
}
Collection<GuiButton> getButtons(){
return WizardryUtilities.flatten(buttons);
}
/**
* Returns true if the given page is within this section, false if not (or if the section is locked).
*/
boolean containsPage(int page){
return this.isUnlocked() && startPage <= page && startPage + pages.size() > page;
}
/**
* Returns true if this section is unlocked for the client player, false if not. Always returns true if
* handbook progression is disabled in the config.
*/
boolean isUnlocked(){
if(Minecraft.getMinecraft().player.isCreative()) return true;
if(!Wizardry.settings.handbookProgression) return true; // Always unlocked if handbook progression is off
if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start
// A section is automatically unlocked if one of its subsections is unlocked
for(Section subsection : subsections.values()){
if(subsection.isUnlocked()) return true;
}
return unlocked;
}
/**
* Returns true if this section has been unlocked and not read yet. Also returns true if any subsections are new.
*/
boolean isNew(){
if(!Wizardry.settings.handbookProgression) return false;
return isNew || this.subsections.values().stream().anyMatch(Section::isNew);
}
/**
* Actually draws the contents of the given section for the given double-page spread. Will do nothing if the
* given page is outside of this section.
*
* @param font The font renderer object.
* @param doublePage The index of the <b>double-page</b> to be drawn.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
*/
// This method is supposed to be 'idiot-proof' in the sense that the code calling it need not check whether the
// section actually needs drawing, so it can just dumbly call draw(...) for all the sections in order.
void draw(FontRenderer font, int doublePage, int left, int top){
// Show/hide buttons
int i = 0;
for(List<GuiButton> list : buttons){
final int i1 = i++;
list.forEach(b -> b.visible = GuiWizardHandbook.singleToDoublePage(startPage + i1) == doublePage);
}
int leftIndex = GuiWizardHandbook.doubleToSinglePage(doublePage, false);
// Relative indices of the pages to be rendered - often these will be outside the section entirely
int[] visiblePages = {leftIndex - startPage, leftIndex - startPage + 1};
for(int page : visiblePages){
if(page >= 0 && page < pages.size()){
List<String> lines = pages.get(page);
int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X);
int y = top + GuiWizardHandbook.TEXT_INSET_Y;
if(centreY) y += GuiWizardHandbook.PAGE_HEIGHT / 2 - lines.size() / 2 * font.FONT_HEIGHT;
for(String line : lines){
if(line.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG)){
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
DrawingUtils.drawTexturedRect(x-1, y-1, 0, GuiWizardHandbook.GUI_HEIGHT, GuiWizardHandbook.PAGE_WIDTH + 2, 9, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT);
}else{
int lx = centreX ? x + GuiWizardHandbook.PAGE_WIDTH / 2 - font.getStringWidth(line) / 2 : x;
font.drawString(line, lx, y, DrawingUtils.BLACK, false);
}
y += font.FONT_HEIGHT;
}
isNew = false; // Now a page has been drawn, the player must have seen it so it's not new any more
}
}
}
/**
* Called on GUI load to format the section, contents tables and other elements, <b>excluding</b> subsections.
* Does not perform any actual drawing.
*
* @param font The font renderer object, for measurement purposes.
* @param startPage The index of the first page (single side, not double-page) of this section.
* @param left The x coordinate of the left side of the GUI.
* @param top The y coordinate of the top of the GUI.
* @return The <b>single-page</b> index of the next blank page after the end of this section.
* @throws JsonSyntaxException if at any point the formatting is found to be invalid.
*/
int format(FontRenderer font, int startPage, int left, int top){
this.buttons.clear();
this.pages.clear();
// FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14.
final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT;
this.startPage = startPage;
// First everything is added to a single list of lines, then it is split into pages.
List<String> lines = new ArrayList<>();
// Adds the header if present
if(!this.title.isEmpty()){
lines.add(this.title);
lines.add(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG);
}
// Adds space for the contents if it exists
if(this.contents != null){
lines.addAll(Collections.nCopies(this.contents.format(font, startPage, lines.size(), left, top), ""));
// Line break between contents and first paragraph
if((lines.size() % maxLineNumber) != 0) lines.add("");
}
if(this.rawText != null){
// Paragraphs are defined as a JSON list because it makes it easier to arrange them properly across pages
// - using multiple line breaks would mean having to find and remove them when at the top of a page.
for(String paragraph : this.rawText){
// (lines.size() % maxLineNumber) gives the number of lines on the current page
// (lines.size() / maxLineNumber) gives the index of the current page minus the value of startPage
// Formats the paragraph
String raw = paragraph; // For error messages
// Images (images must be separate paragraphs)
if(paragraph.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.IMAGE_TAG)){
String[] arguments = paragraph.split("\\s", 2);
if(arguments.length < 2) throw new JsonSyntaxException("Missing image name in string "
+ StringUtils.abbreviate(raw, 50));
Image image = GuiWizardHandbook.images.get(arguments[1]);
if(image == null) throw new JsonSyntaxException("Image with id " + arguments[1] + " is undefined");
// Starts a new page if the image will not fit on the current one
if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT){
// Remaining number of lines on the page
lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), ""));
}
if(image.getWidth() > GuiWizardHandbook.PAGE_WIDTH) Wizardry.logger.warn("Image with id " + arguments[1]
+ "has a width (" + image.getWidth() + ") greater than the maximum page width (" + GuiWizardHandbook.PAGE_WIDTH
+ "), it will extend beyond the page area.");
if(image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT) Wizardry.logger.warn("Image with id " + arguments[1]
+ "has a height (" + image.getHeight(font) + ") greater than the maximum page height (" + GuiWizardHandbook.PAGE_HEIGHT
+ "), it will extend beyond the page area.");
int page = startPage + (lines.size() / maxLineNumber);
image.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - image.getWidth() / 2
+ (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X),
GuiWizardHandbook.TEXT_INSET_Y + (lines.size() % maxLineNumber) * font.FONT_HEIGHT);
// Height of the image in lines, rounded up
// Uses a single space instead of an empty string so that the page trimming doesn't remove them
lines.addAll(Collections.nCopies(image.getHeight(font) / font.FONT_HEIGHT, " "));
lines.add(""); // The last one is removable though, since it's actually extra space
// Recipes (recipes must be separate paragraphs)
}else if(paragraph.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RECIPE_TAG)){
String[] arguments = paragraph.split("\\s", 2);
if(arguments.length < 2) throw new JsonSyntaxException("Missing recipe name in string "
+ StringUtils.abbreviate(raw, 50));
CraftingRecipe recipe = GuiWizardHandbook.recipes.get(arguments[1]);
if(recipe == null) throw new JsonSyntaxException("Recipe with id " + arguments[1] + " is undefined");
// Starts a new page if the recipe will not fit on the current one
if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + CraftingRecipe.HEIGHT > GuiWizardHandbook.PAGE_HEIGHT){
// Remaining number of lines on the page, plus the first blank one on the new page
lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), " "));
}
int page = startPage + (lines.size() / maxLineNumber);
if(lines.size() % maxLineNumber == 0) lines.add(" ");
int startLine = lines.size() % maxLineNumber - 1;
recipe.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - CraftingRecipe.WIDTH / 2
+ (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X),
GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT);
// Height of the recipe in lines, rounded up
// Uses a single space instead of an empty string so that the page trimming doesn't remove them
lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT - 1, " "));
// This time we're not adding an extra space because it's not really needed
}else{ // All other paragraphs
// Formatting
for(Map.Entry<String, String> entry : GuiWizardHandbook.FORMAT_TAGS.entrySet()){
paragraph = paragraph.replace(GuiWizardHandbook.FORMAT_MARKER + entry.getKey(), entry.getValue());
}
// Hyperlinks
int linkStart;
while((linkStart = paragraph.indexOf(GuiWizardHandbook.HYPERLINK_MARKER)) > -1){ // Ooh an assignment and a comparison in one...
int linkEnd = paragraph.indexOf(GuiWizardHandbook.HYPERLINK_MARKER, linkStart + 1);
if(linkEnd < 0) throw new JsonSyntaxException("Un-closed hyperlink marker in string "
+ StringUtils.abbreviate(raw, 50));
List<String> upToLink = font.listFormattedStringToWidth(paragraph.substring(0, linkStart), GuiWizardHandbook.PAGE_WIDTH);
String linkRaw = paragraph.substring(linkStart, linkEnd + 1);
String[] arguments = paragraph.substring(linkStart + 1, linkEnd).split("\\s", 2);
String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0].substring(1); // substring(1) to remove the @
// The index of the single page currently being formatted, relative to the section
int pageRelative = (lines.size() + upToLink.size() - 1) / maxLineNumber;
// The overall index of the single page currently being formatted
int page = startPage + pageRelative;
// The line number on this page
int lineNumber = (lines.size() + upToLink.size() - 1) % maxLineNumber;
int x = GuiWizardHandbook.isRightPage(page) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X;
int y = top + GuiWizardHandbook.TEXT_INSET_Y + lineNumber * font.FONT_HEIGHT;
// Adds any missing sub-lists
while(this.buttons.size() <= pageRelative){
this.buttons.add(new ArrayList<>());
}
// The button id only does what you use it for, so we're just not using it at all.
this.buttons.get(pageRelative).add(GuiButtonHyperlink.create(x, y, font, upToLink, arguments, suffix, maxLineNumber - lineNumber - 1, GuiWizardHandbook.isRightPage(page)));
// The link button should exactly overlay the display rawText in the main string
// If the link has no display rawText specified, it displays the unformatted target string
paragraph = paragraph.replace(linkRaw, arguments[arguments.length - 1]);
}
lines.addAll(font.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH));
}
// Line break between paragraphs (the last one will just be deleted later)
if((lines.size() % maxLineNumber) != 0) lines.add("");
}
}
// Splits lines into pages
List<String> page = new ArrayList<>();
pages.add(page);
while(!lines.isEmpty()){
if(page.size() == maxLineNumber){
// Removes blank lines at the end of the page
while(page.get(page.size() - 1).isEmpty()) page.remove(page.size() - 1);
// Adds a new page
pages.add(page = new ArrayList<>());
}
String line = lines.remove(0);
// Prevents blank lines at the start of the page
if(!page.isEmpty() || !line.isEmpty()) page.add(line);
}
return startPage + pages.size();
}
/**
* Parses the given JSON object and constructs a new {@code Section} from it, setting all the relevant fields
* and references. This method converts the JSON object to a {@code Section} object and retrieves any resources;
* the section is not formatted in any way until GUI load, in {@link Section#format(FontRenderer, int, int, int)}.
*
* @param json A JSON object representing the section to be constructed. This must contain at least a "title"
* string.
* @return The resulting {@code Section} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
static Section fromJson(JsonObject json){
Section section = new Section();
section.title = JsonUtils.getString(json, "title", "");
if(JsonUtils.hasField(json, "include_in_contents")){
String id = JsonUtils.getString(json, "include_in_contents");
Contents belongsTo = GuiWizardHandbook.contentsList.get(id);
if(belongsTo == null){
throw new JsonSyntaxException("Expected include_in_contents to be the id of a previously defined contents, but no contents with the id " + id + " exists yet.");
}else{
belongsTo.addEntry(section);
}
}
if(JsonUtils.hasField(json, "contents")){
section.contents = Contents.fromJson(section, JsonUtils.getJsonObject(json, "contents"));
GuiWizardHandbook.contentsList.put(section.contents.id, section.contents);
}
if(JsonUtils.hasField(json, "text")){
section.rawText = Streams.stream(JsonUtils.getJsonArray(json, "text"))
.map(e -> JsonUtils.getString(e, "element of array rawText"))
.toArray(String[]::new);
}
if(JsonUtils.hasField(json, "triggers")){
section.triggers = Streams.stream(JsonUtils.getJsonArray(json, "triggers"))
.map(e -> new ResourceLocation(JsonUtils.getString(e, "element of array triggers")))
.toArray(ResourceLocation[]::new);
// TODO: Can we validate this and throw a JSON exception if no such advancement exists?
}
if(JsonUtils.hasField(json, "centre")){
JsonObject centre = JsonUtils.getJsonObject(json,"centre");
section.centreX = JsonUtils.getBoolean(centre, "x", false);
section.centreY = JsonUtils.getBoolean(centre, "y", false);
}
// The only benefit of having subsections (other than logical grouping) is that the parent section can
// automatically be unlocked if one of the subsections is.
if(JsonUtils.hasField(json, "sections")){
populate(section.subsections, json);
}
return section;
}
static void populate(Map<String, Section> map, JsonObject json){
JsonObject sectionsObject = JsonUtils.getJsonObject(json, "sections");
// Need to iterate over these since we don't know what they're called or how many there are
for(Map.Entry<String, JsonElement> entry : sectionsObject.entrySet()){
String key = entry.getKey(); // Find out what each element is called, this will be the sections map key
Section section = fromJson(entry.getValue().getAsJsonObject());
map.put(key, section);
map.putAll(section.subsections);
}
}
/**
* Called on login and advancement completion to update this section's unlock status and display toast
* notifications if applicable.
*/
public void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){
if(triggers == null) return;
List<ResourceLocation> completed = new ArrayList<>(Arrays.asList(completedAdvancements));
completed.retainAll(Arrays.asList(triggers));
// Only shows the toast when the section was locked before and is now unlocked
if(!this.unlocked && !completed.isEmpty() && showToasts && Wizardry.settings.handbookProgression){
// Mmmmm toast...
Minecraft minecraft = Minecraft.getMinecraft();
minecraft.getToastGui().add(new HandbookToast(this));
this.isNew = true;
}
// Currently, this will not take subsections into account
this.unlocked = !completed.isEmpty();
}
}
@@ -0,0 +1,234 @@
package electroblob.wizardry.client.model;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.fml.client.FMLClientHandler;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Custom baked model that stores a list of texture names and sets the lighting to full brightness for any quads
* with one of those textures. Most of this code was copied and adapted from refined storage, which is licensed under
* the MIT license. https://github.com/raoulvdberge/refinedstorage<br>
* <br>
* <i>N.B. This doesn't cover item models, and all of the code/model solutions to this that I have tried haven't worked.
* However, I noticed that the shade tag seems to work perfectly for items, so instead I've simply duplicated the block
* models into the item models folder and add {@code "shade":false} where appropriate. (If it works, it works, right?)</i>
*
* @author Electroblob
* @author raoulvdberge
*/
public class BakedModelGlowingOverlay implements IBakedModel {
// Something something something cache, says Forge
// This breaks randomised block models (because it's cached, duh...) but simply including the rand parameter will
// completely defeat the point of the cache so I need some way of storing the randomised model variants... hmmm...
// See WeightedBakedModel for more on randomisation (it's pretty similar to my 1.7 implementation from years ago)
// private class CacheKey {
//
// private IBakedModel base;
// private String suffix;
// private IBlockState state;
// private EnumFacing side;
//
// public CacheKey(IBakedModel base, String suffix, IBlockState state, EnumFacing side){
// this.base = base;
// this.suffix = suffix;
// this.state = state;
// this.side = side;
// }
//
// @Override
// public boolean equals(Object o){
//
// if(this == o) return true;
// if(o == null || getClass() != o.getClass()) return false;
//
// CacheKey cacheKey = (CacheKey)o;
//
// if(cacheKey.side != side) return false;
// if(!state.equals(cacheKey.state)) return false;
//
// return true;
// }
//
// @Override
// public int hashCode() {
// return state.hashCode() + (31 * (side != null ? side.hashCode() : 0));
// }
// }
//
// private static final LoadingCache<CacheKey, List<BakedQuad>> CACHE = CacheBuilder.newBuilder().build(new CacheLoader<CacheKey, List<BakedQuad>>() {
// @Override
// public List<BakedQuad> load(CacheKey key) {
// return transformQuads(key.base.getQuads(key.state, key.side, 0), key.suffix);
// }
// });
private final IBakedModel delegate;
private String suffix;
public BakedModelGlowingOverlay(IBakedModel delegate, String suffix){
this.delegate = delegate;
this.suffix = suffix;
}
@Override
public List<BakedQuad> getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand){
if(state == null) return delegate.getQuads(state, side, rand);
return transformQuads(delegate.getQuads(state, side, rand), suffix);
//return CACHE.getUnchecked(new CacheKey(delegate, suffix, state instanceof IExtendedBlockState ? ((IExtendedBlockState) state).getClean() : state, side));
}
// I would write these myself but I'd end up with almost the exact same thing anyway
// They replace the quads from the original (delegate) model with full-brightness quads if their texture has the given suffix
private static List<BakedQuad> transformQuads(List<BakedQuad> oldQuads, String suffix){
List<BakedQuad> quads = new ArrayList<>(oldQuads);
for(int i = 0; i < quads.size(); ++i){
BakedQuad quad = quads.get(i);
if(quad.getSprite().getIconName().endsWith(suffix)){
quads.set(i, transformQuad(quad, 0.007F)); // What's the significance of 0.007?
}
}
return quads;
}
private static BakedQuad transformQuad(BakedQuad quad, float light){
if(isLightMapDisabled()){
return quad;
}
VertexFormat newFormat = getFormatWithLightMap(quad.getFormat());
UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(newFormat);
VertexLighterFlat trans = new VertexLighterFlat(Minecraft.getMinecraft().getBlockColors()) {
@Override
protected void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z){
lightmap[0] = light;
lightmap[1] = light;
}
@Override
public void setQuadTint(int tint){
// NO OP
}
};
trans.setParent(builder);
quad.pipe(trans);
builder.setQuadTint(quad.getTintIndex());
builder.setQuadOrientation(quad.getFace());
builder.setTexture(quad.getSprite());
builder.setApplyDiffuseLighting(false);
return builder.build();
}
@Override
public boolean isAmbientOcclusion(){
return delegate.isAmbientOcclusion();
}
@Override
public boolean isGui3d(){
return delegate.isGui3d();
}
@Override
public boolean isBuiltInRenderer(){
return delegate.isBuiltInRenderer();
}
@Override
public TextureAtlasSprite getParticleTexture(){
return delegate.getParticleTexture();
}
@Override
public ItemCameraTransforms getItemCameraTransforms(){
return delegate.getItemCameraTransforms();
}
@Override
public ItemOverrideList getOverrides(){
return delegate.getOverrides();//BakedModelItemOverride.instance;
}
@Override
public boolean isAmbientOcclusion(IBlockState state){
return delegate.isAmbientOcclusion(state);
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType){
return delegate.handlePerspective(cameraTransformType);
}
// Utilities
private static boolean isLightMapDisabled(){
return FMLClientHandler.instance().hasOptifine() || !ForgeModContainer.forgeLightPipelineEnabled;
}
private static final VertexFormat ITEM_FORMAT_WITH_LIGHTMAP = new VertexFormat(DefaultVertexFormats.ITEM).addElement(DefaultVertexFormats.TEX_2S);
private static VertexFormat getFormatWithLightMap(VertexFormat format){
if(isLightMapDisabled()){
return format;
}
if(format == DefaultVertexFormats.BLOCK){
return DefaultVertexFormats.BLOCK;
}else if(format == DefaultVertexFormats.ITEM){
return ITEM_FORMAT_WITH_LIGHTMAP;
}else if(!format.hasUvOffset(1)){
VertexFormat result = new VertexFormat(format);
result.addElement(DefaultVertexFormats.TEX_2S);
return result;
}
return format;
}
// Bit of a weird way of doing things if you ask me, but as far as I can tell it's how you're supposed to do it
// public static final class BakedModelItemOverride extends ItemOverrideList {
//
// // This class doesn't contain any data of its own so it can be a singleton
// public static final BakedModelItemOverride instance = new BakedModelItemOverride();
//
// private BakedModelItemOverride(){
// super(ImmutableList.of()); // We're not using the list functionality
// }
//
// @Override
// public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){
// return new BakedModelGlowingOverlay(originalModel, "overlay"); // Bish bash bosh
// }
// }
}
@@ -0,0 +1,49 @@
package electroblob.wizardry.client.model;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityArmorStand;
/**
* Fixes custom armour models 'breathing' on the stand and rotates the helmet properly.
* @author Shadows-of-Fire
* @since Wizardry 4.1.2
*/
public class ModelArmourFixer extends ModelBiped {
public ModelArmourFixer(float modelSize, float rotationYOffset, int textureWidth, int textureHeight) {
super(modelSize, rotationYOffset, textureWidth, textureHeight);
}
@Override
public void setRotationAngles(float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, float scaleFactor, Entity entityIn) {
// [VanillaCopy] ModelArmorStandArmor
// this prevents helmets from always facing south, and the armor "breathing" on the stand
if (entityIn instanceof EntityArmorStand) {
EntityArmorStand entityarmorstand = (EntityArmorStand) entityIn;
this.bipedHead.rotateAngleX = 0.017453292F * entityarmorstand.getHeadRotation().getX();
this.bipedHead.rotateAngleY = 0.017453292F * entityarmorstand.getHeadRotation().getY();
this.bipedHead.rotateAngleZ = 0.017453292F * entityarmorstand.getHeadRotation().getZ();
this.bipedHead.setRotationPoint(0.0F, 1.0F, 0.0F);
this.bipedBody.rotateAngleX = 0.017453292F * entityarmorstand.getBodyRotation().getX();
this.bipedBody.rotateAngleY = 0.017453292F * entityarmorstand.getBodyRotation().getY();
this.bipedBody.rotateAngleZ = 0.017453292F * entityarmorstand.getBodyRotation().getZ();
this.bipedLeftArm.rotateAngleX = 0.017453292F * entityarmorstand.getLeftArmRotation().getX();
this.bipedLeftArm.rotateAngleY = 0.017453292F * entityarmorstand.getLeftArmRotation().getY();
this.bipedLeftArm.rotateAngleZ = 0.017453292F * entityarmorstand.getLeftArmRotation().getZ();
this.bipedRightArm.rotateAngleX = 0.017453292F * entityarmorstand.getRightArmRotation().getX();
this.bipedRightArm.rotateAngleY = 0.017453292F * entityarmorstand.getRightArmRotation().getY();
this.bipedRightArm.rotateAngleZ = 0.017453292F * entityarmorstand.getRightArmRotation().getZ();
this.bipedLeftLeg.rotateAngleX = 0.017453292F * entityarmorstand.getLeftLegRotation().getX();
this.bipedLeftLeg.rotateAngleY = 0.017453292F * entityarmorstand.getLeftLegRotation().getY();
this.bipedLeftLeg.rotateAngleZ = 0.017453292F * entityarmorstand.getLeftLegRotation().getZ();
this.bipedLeftLeg.setRotationPoint(1.9F, 11.0F, 0.0F);
this.bipedRightLeg.rotateAngleX = 0.017453292F * entityarmorstand.getRightLegRotation().getX();
this.bipedRightLeg.rotateAngleY = 0.017453292F * entityarmorstand.getRightLegRotation().getY();
this.bipedRightLeg.rotateAngleZ = 0.017453292F * entityarmorstand.getRightLegRotation().getZ();
this.bipedRightLeg.setRotationPoint(-1.9F, 11.0F, 0.0F);
copyModelAngles(this.bipedHead, this.bipedHeadwear);
} else super.setRotationAngles(limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scaleFactor, entityIn);
}
}
@@ -5,64 +5,71 @@ import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity;
public class ModelHammer extends ModelBase {
ModelRenderer Shape1;
ModelRenderer Shape2;
ModelRenderer Shape3;
ModelRenderer Shape4;
ModelRenderer Shape5;
ModelRenderer Shape6;
ModelRenderer hammerHead;
ModelRenderer handle;
ModelRenderer handleEnd;
ModelRenderer handleBase;
ModelRenderer ring1;
ModelRenderer ring2;
public ModelHammer(){
textureWidth = 64;
textureHeight = 64;
Shape1 = new ModelRenderer(this, 0, 0);
Shape1.addBox(0F, 0F, 0F, 20, 12, 12);
Shape1.setRotationPoint(-10F, 12F, -6F);
Shape1.setTextureSize(64, 64);
Shape1.mirror = true;
setRotation(Shape1, 0F, 0F, 0F);
Shape2 = new ModelRenderer(this, 0, 24);
Shape2.addBox(0F, 0F, 0F, 4, 14, 4);
Shape2.setRotationPoint(-2F, -2F, -2F);
Shape2.setTextureSize(64, 64);
Shape2.mirror = true;
setRotation(Shape2, 0F, 0F, 0F);
Shape3 = new ModelRenderer(this, 0, 49);
Shape3.addBox(0F, 0F, 0F, 5, 5, 5);
Shape3.setRotationPoint(-2.5F, -7F, -2.5F);
Shape3.setTextureSize(64, 64);
Shape3.mirror = true;
setRotation(Shape3, 0F, 0F, 0F);
Shape4 = new ModelRenderer(this, 0, 42);
Shape4.addBox(0F, 0F, 0F, 5, 2, 5);
Shape4.setRotationPoint(-2.5F, 10F, -2.5F);
Shape4.setTextureSize(64, 64);
Shape4.mirror = true;
setRotation(Shape4, 0F, 0F, 0F);
Shape5 = new ModelRenderer(this, 20, 24);
Shape5.addBox(0F, 0F, 0F, 2, 14, 14);
Shape5.setRotationPoint(-8F, 11F, -7F);
Shape5.setTextureSize(64, 64);
Shape5.mirror = true;
setRotation(Shape5, 0F, 0F, 0F);
Shape6 = new ModelRenderer(this, 20, 24);
Shape6.addBox(0F, 0F, 0F, 2, 14, 14);
Shape6.setRotationPoint(6F, 11F, -7F);
Shape6.setTextureSize(64, 64);
Shape6.mirror = true;
setRotation(Shape6, 0F, 0F, 0F);
hammerHead = new ModelRenderer(this, 0, 0);
hammerHead.addBox(0F, 0F, 0F, 20, 12, 12);
hammerHead.setRotationPoint(-10F, 12F, -6F);
hammerHead.setTextureSize(64, 64);
hammerHead.mirror = true;
setRotation(hammerHead, 0F, 0F, 0F);
handle = new ModelRenderer(this, 0, 24);
handle.addBox(0F, 0F, 0F, 4, 14, 4);
handle.setRotationPoint(-2F, -2F, -2F);
handle.setTextureSize(64, 64);
handle.mirror = true;
setRotation(handle, 0F, 0F, 0F);
handleEnd = new ModelRenderer(this, 0, 49);
handleEnd.addBox(0F, 0F, 0F, 5, 5, 5);
handleEnd.setRotationPoint(-2.5F, -7F, -2.5F);
handleEnd.setTextureSize(64, 64);
handleEnd.mirror = true;
setRotation(handleEnd, 0F, 0F, 0F);
handleBase = new ModelRenderer(this, 0, 42);
handleBase.addBox(0F, 0F, 0F, 5, 2, 5);
handleBase.setRotationPoint(-2.5F, 10F, -2.5F);
handleBase.setTextureSize(64, 64);
handleBase.mirror = true;
setRotation(handleBase, 0F, 0F, 0F);
ring1 = new ModelRenderer(this, 20, 24);
ring1.addBox(0F, 0F, 0F, 2, 14, 14);
ring1.setRotationPoint(-8F, 11F, -7F);
ring1.setTextureSize(64, 64);
ring1.mirror = true;
setRotation(ring1, 0F, 0F, 0F);
ring2 = new ModelRenderer(this, 20, 24);
ring2.addBox(0F, 0F, 0F, 2, 14, 14);
ring2.setRotationPoint(6F, 11F, -7F);
ring2.setTextureSize(64, 64);
ring2.mirror = true;
setRotation(ring2, 0F, 0F, 0F);
}
public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){
super.render(entity, f, f1, f2, f3, f4, f5);
setRotationAngles(f, f1, f2, f3, f4, f5, entity);
Shape1.render(f5);
Shape2.render(f5);
Shape3.render(f5);
Shape4.render(f5);
Shape5.render(f5);
Shape6.render(f5);
hammerHead.render(f5);
handle.render(f5);
handleEnd.render(f5);
handleBase.render(f5);
ring1.render(f5);
ring2.render(f5);
}
private void setRotation(ModelRenderer model, float x, float y, float z){
@@ -1,18 +1,16 @@
package electroblob.wizardry.client.model;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import electroblob.wizardry.entity.living.EntityIceGiant;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
//@SideOnly(Side.CLIENT)
public class ModelIceGiant extends ModelBase {
/** The head model for the iron golem. */
public ModelRenderer iceGiantHead;
@@ -97,9 +97,9 @@ public class ModelWizard extends ModelBiped {
setRotation(Shape13, 0F, 0F, 0F);
// Makes head bits move with head
// bipedHead.addChild(Shape5);
// bipedHead.addChild(hatSegment4);
bipedHead.addChild(beard);
// bipedHead.addChild(Shape7);
// bipedHead.addChild(hatSegment6);
// bipedHead.addChild(Shape8);
// bipedHead.addChild(Shape9);
// bipedHead.addChild(Shape10);
@@ -116,8 +116,8 @@ public class ModelWizard extends ModelBiped {
/* public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) {
* super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity);
* bipedRightLeg.render(f5); bipedLeftLeg.render(f5); bipedBody.render(f5); bipedLeftArm.render(f5);
* bipedRightArm.render(f5); bipedHead.render(f5); Shape5.render(f5); Shape8.render(f5); Shape9.render(f5);
* Shape10.render(f5); Shape7.render(f5); Shape11.render(f5); Shape12.render(f5); Shape6.render(f5);
* bipedRightArm.render(f5); bipedHead.render(f5); hatSegment4.render(f5); Shape8.render(f5); Shape9.render(f5);
* Shape10.render(f5); hatSegment6.render(f5); Shape11.render(f5); Shape12.render(f5); hatSegment5.render(f5);
* Shape13.render(f5); } */
private void setRotation(ModelRenderer model, float x, float y, float z){
model.rotateAngleX = x;
@@ -1,100 +1,110 @@
package electroblob.wizardry.client.model;
import net.minecraft.client.model.ModelBiped;
import electroblob.wizardry.block.BlockStatue;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity;
public class ModelWizardArmour extends ModelBiped {
ModelRenderer Shape1;
ModelRenderer Shape2;
ModelRenderer Shape3;
ModelRenderer Shape4;
ModelRenderer Shape5;
ModelRenderer Shape6;
ModelRenderer Shape7;
public class ModelWizardArmour extends ModelArmourFixer {
ModelRenderer hatBrim;
ModelRenderer hatSegment1;
ModelRenderer hatSegment2;
ModelRenderer hatSegment3;
ModelRenderer hatSegment4;
ModelRenderer hatSegment5;
ModelRenderer hatSegment6;
ModelRenderer robe;
public ModelWizardArmour(float scale){
public ModelWizardArmour(float delta){
super(scale, 0, 64, 64);
super(delta, 0, 64, 64);
// This is necessary to stop the head from scaling.
this.bipedHead = new ModelRenderer(this, 0, 0);
this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.1f);
// The hat layer has an offset of 0.5, so 0.6 is about the smallest we can get away with
this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.6f);
this.bipedHead.setRotationPoint(0.0F, 0.0F + 0, 0.0F);
Shape1 = new ModelRenderer(this, -16, 32);
Shape1.addBox(-8F, -7F, -8F, 16, 0, 16);
Shape1.setRotationPoint(0F, 0F, 0F);
Shape1.setTextureSize(64, 64);
Shape1.mirror = true;
setRotation(Shape1, 0F, 0F, 0F);
hatBrim = new ModelRenderer(this, 0, 47);
// Making the height 1 stops the top and bottom z-fighting when the hat is enchanted
hatBrim.addBox(-8F, -6.85F, -8F, 16, 1, 16, 0.6f);
hatBrim.setRotationPoint(0F, 0F, 0F);
hatBrim.setTextureSize(64, 64);
hatBrim.mirror = true;
setRotation(hatBrim, 0F, 0F, 0F);
Shape2 = new ModelRenderer(this, 0, 48);
Shape2.addBox(0F, 0F, 0F, 6, 2, 6);
Shape2.setRotationPoint(-3F, -10F, -3F);
Shape2.setTextureSize(64, 64);
Shape2.mirror = true;
setRotation(Shape2, -0.1396263F, 0F, 0F);
hatSegment1 = new ModelRenderer(this, 0, 32);
hatSegment1.addBox(0F, 0F, 0F, 6, 2, 6, 0.2f);
hatSegment1.setRotationPoint(-3F, -10.6F, -3F);
hatSegment1.setTextureSize(64, 64);
hatSegment1.mirror = true;
setRotation(hatSegment1, -0.1396263F, 0F, 0F);
Shape3 = new ModelRenderer(this, 0, 56);
Shape3.addBox(0F, 0F, 0F, 5, 2, 5);
Shape3.setRotationPoint(-2.5F, -11.53333F, -2F);
Shape3.setTextureSize(64, 64);
Shape3.mirror = true;
setRotation(Shape3, -0.2443461F, 0F, 0F);
hatSegment2 = new ModelRenderer(this, 0, 40);
hatSegment2.addBox(0F, 0F, 0F, 5, 2, 5, 0.1f);
hatSegment2.setRotationPoint(-2.5F, -12.13333F, -2F);
hatSegment2.setTextureSize(64, 64);
hatSegment2.mirror = true;
setRotation(hatSegment2, -0.2443461F, 0F, 0F);
Shape4 = new ModelRenderer(this, 24, 48);
Shape4.addBox(0F, 0F, 0F, 4, 2, 4);
Shape4.setRotationPoint(-2F, -13F, -1F);
Shape4.setTextureSize(64, 64);
Shape4.mirror = true;
setRotation(Shape4, -0.4014257F, 0F, 0F);
hatSegment3 = new ModelRenderer(this, 24, 32);
hatSegment3.addBox(0F, 0F, 0F, 4, 2, 4);
hatSegment3.setRotationPoint(-2F, -13.6F, -1F);
hatSegment3.setTextureSize(64, 64);
hatSegment3.mirror = true;
setRotation(hatSegment3, -0.4014257F, 0F, 0F);
Shape5 = new ModelRenderer(this, 24, 54);
Shape5.addBox(0F, 0F, 0F, 3, 2, 3);
Shape5.setRotationPoint(-1.5F, -14F, 0F);
Shape5.setTextureSize(64, 64);
Shape5.mirror = true;
setRotation(Shape5, -0.5759587F, 0F, 0F);
hatSegment4 = new ModelRenderer(this, 24, 38);
hatSegment4.addBox(0F, 0F, 0F, 3, 2, 3);
hatSegment4.setRotationPoint(-1.5F, -14.6F, 0F);
hatSegment4.setTextureSize(64, 64);
hatSegment4.mirror = true;
setRotation(hatSegment4, -0.5759587F, 0F, 0F);
Shape6 = new ModelRenderer(this, 20, 59);
Shape6.addBox(0F, 0F, 0F, 2, 2, 2);
Shape6.setRotationPoint(-1F, -14F, 0F);
Shape6.setTextureSize(64, 64);
Shape6.mirror = true;
setRotation(Shape6, 0.3316126F, 0F, 0F);
hatSegment5 = new ModelRenderer(this, 20, 43);
hatSegment5.addBox(0F, 0F, 0F, 2, 2, 2);
hatSegment5.setRotationPoint(-1F, -14.6F, 0F);
hatSegment5.setTextureSize(64, 64);
hatSegment5.mirror = true;
setRotation(hatSegment5, 0.3316126F, 0F, 0F);
Shape7 = new ModelRenderer(this, 28, 59);
Shape7.addBox(0F, 0F, 0F, 1, 1, 3);
Shape7.setRotationPoint(-0.5F, -14.5F, 2F);
Shape7.setTextureSize(64, 64);
Shape7.mirror = true;
setRotation(Shape7, -0.5585054F, 0F, 0F);
hatSegment6 = new ModelRenderer(this, 28, 43);
hatSegment6.addBox(0F, 0F, 0F, 1, 1, 3);
hatSegment6.setRotationPoint(-0.5F, -15.1F, 2F);
hatSegment6.setTextureSize(64, 64);
hatSegment6.mirror = true;
setRotation(hatSegment6, -0.5585054F, 0F, 0F);
// The robe is now the body
bipedBody = new ModelRenderer(this, 40, 42);
bipedBody.addBox(-4F, 0F, -2F, 8, 18, 4, scale);
bipedBody = new ModelRenderer(this, 16, 16);
bipedBody.addBox(-4F, 0F, -2F, 8, 11, 4, delta);
bipedBody.setRotationPoint(0F, 0F, 0F);
bipedBody.setTextureSize(64, 64);
bipedBody.mirror = true;
setRotation(bipedBody, 0F, 0F, 0F);
robe = new ModelRenderer(this, 40, 32);
robe.addBox(-4F, 0F, -2F, 8, 7, 4, delta);
robe.setRotationPoint(0F, 12, 0F); // 12.5 accounts for the expansion of each box
robe.setTextureSize(64, 64);
robe.mirror = true;
setRotation(robe, 0F, 0F, 0F);
// Makes the hat rotate with the head.
bipedHead.addChild(Shape1);
bipedHead.addChild(Shape2);
bipedHead.addChild(Shape3);
bipedHead.addChild(Shape4);
bipedHead.addChild(Shape5);
bipedHead.addChild(Shape6);
bipedHead.addChild(Shape7);
// Makes the robe move with the body
// bipedBody.addChild(robe);
bipedHead.addChild(hatBrim);
bipedHead.addChild(hatSegment1);
bipedHead.addChild(hatSegment2);
bipedHead.addChild(hatSegment3);
bipedHead.addChild(hatSegment4);
bipedHead.addChild(hatSegment5);
bipedHead.addChild(hatSegment6);
}
public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){
if(entity.isInvisible() && !entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)
&& !entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY)) return;
super.render(entity, f, f1, f2, f3, f4, f5);
setRotationAngles(f, f1, f2, f3, f4, f5, entity);
this.robe.render(f5);
}
private void setRotation(ModelRenderer model, float x, float y, float z){
@@ -105,6 +115,20 @@ public class ModelWizardArmour extends ModelBiped {
public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity){
super.setRotationAngles(f, f1, f2, f3, f4, f5, entity);
this.robe.showModel = this.bipedBody.showModel;
if(this.isSneak){
//this.robe.rotationPointY = 10.5f;
this.robe.rotationPointZ = 4;
}else{
//this.robe.rotationPointY = 12.5f;
this.robe.rotationPointZ = 0;
}
// The bottom part of the robe takes the y rotation from the rest of the robe but the x/z rotation
// from the average of the two legs
this.robe.rotateAngleX = (this.bipedLeftLeg.rotateAngleX + this.bipedRightLeg.rotateAngleX) / 2f;
this.robe.rotateAngleY = this.bipedBody.rotateAngleY;
this.robe.rotateAngleZ = (this.bipedLeftLeg.rotateAngleZ + this.bipedRightLeg.rotateAngleZ) / 2f;
}
}
@@ -1,198 +0,0 @@
package electroblob.wizardry.client.model;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
/**
* Class responsible for registering all of wizardry's item (and itemblock) models.
*
* @author Electroblob
* @since Wizardry 2.1
*/
@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryItemModels {
@SubscribeEvent
public static void register(ModelRegistryEvent event){
// ItemBlocks
registerItemModel(Item.getItemFromBlock(WizardryBlocks.arcane_workbench));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_ore));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_flower));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.transportation_stone));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_block));
// Items
registerItemModel(WizardryItems.magic_crystal);
registerItemModel(WizardryItems.magic_wand);
registerItemModel(WizardryItems.apprentice_wand);
registerItemModel(WizardryItems.advanced_wand);
registerItemModel(WizardryItems.master_wand);
registerItemModel(WizardryItems.spell_book);
// Wildcard registered for wizard trades.
registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal");
registerItemModel(WizardryItems.arcane_tome);
registerItemModel(WizardryItems.wizard_handbook);
registerItemModel(WizardryItems.basic_fire_wand);
registerItemModel(WizardryItems.basic_ice_wand);
registerItemModel(WizardryItems.basic_lightning_wand);
registerItemModel(WizardryItems.basic_necromancy_wand);
registerItemModel(WizardryItems.basic_earth_wand);
registerItemModel(WizardryItems.basic_sorcery_wand);
registerItemModel(WizardryItems.basic_healing_wand);
registerItemModel(WizardryItems.apprentice_fire_wand);
registerItemModel(WizardryItems.apprentice_ice_wand);
registerItemModel(WizardryItems.apprentice_lightning_wand);
registerItemModel(WizardryItems.apprentice_necromancy_wand);
registerItemModel(WizardryItems.apprentice_earth_wand);
registerItemModel(WizardryItems.apprentice_sorcery_wand);
registerItemModel(WizardryItems.apprentice_healing_wand);
registerItemModel(WizardryItems.advanced_fire_wand);
registerItemModel(WizardryItems.advanced_ice_wand);
registerItemModel(WizardryItems.advanced_lightning_wand);
registerItemModel(WizardryItems.advanced_necromancy_wand);
registerItemModel(WizardryItems.advanced_earth_wand);
registerItemModel(WizardryItems.advanced_sorcery_wand);
registerItemModel(WizardryItems.advanced_healing_wand);
registerItemModel(WizardryItems.master_fire_wand);
registerItemModel(WizardryItems.master_ice_wand);
registerItemModel(WizardryItems.master_lightning_wand);
registerItemModel(WizardryItems.master_necromancy_wand);
registerItemModel(WizardryItems.master_earth_wand);
registerItemModel(WizardryItems.master_sorcery_wand);
registerItemModel(WizardryItems.master_healing_wand);
registerItemModel(WizardryItems.spectral_sword);
registerItemModel(WizardryItems.spectral_pickaxe);
registerItemModel(WizardryItems.spectral_bow);
registerItemModel(WizardryItems.mana_flask);
registerItemModel(WizardryItems.storage_upgrade);
registerItemModel(WizardryItems.siphon_upgrade);
registerItemModel(WizardryItems.condenser_upgrade);
registerItemModel(WizardryItems.range_upgrade);
registerItemModel(WizardryItems.duration_upgrade);
registerItemModel(WizardryItems.cooldown_upgrade);
registerItemModel(WizardryItems.blast_upgrade);
registerItemModel(WizardryItems.attunement_upgrade);
registerItemModel(WizardryItems.flaming_axe);
registerItemModel(WizardryItems.frost_axe);
registerItemModel(WizardryItems.firebomb);
registerItemModel(WizardryItems.poison_bomb);
registerItemModel(WizardryItems.blank_scroll);
registerItemModel(WizardryItems.scroll);
registerItemModel(WizardryItems.armour_upgrade);
registerItemModel(WizardryItems.magic_silk);
registerItemModel(WizardryItems.wizard_hat);
registerItemModel(WizardryItems.wizard_robe);
registerItemModel(WizardryItems.wizard_leggings);
registerItemModel(WizardryItems.wizard_boots);
registerItemModel(WizardryItems.wizard_hat_fire);
registerItemModel(WizardryItems.wizard_robe_fire);
registerItemModel(WizardryItems.wizard_leggings_fire);
registerItemModel(WizardryItems.wizard_boots_fire);
registerItemModel(WizardryItems.wizard_hat_ice);
registerItemModel(WizardryItems.wizard_robe_ice);
registerItemModel(WizardryItems.wizard_leggings_ice);
registerItemModel(WizardryItems.wizard_boots_ice);
registerItemModel(WizardryItems.wizard_hat_lightning);
registerItemModel(WizardryItems.wizard_robe_lightning);
registerItemModel(WizardryItems.wizard_leggings_lightning);
registerItemModel(WizardryItems.wizard_boots_lightning);
registerItemModel(WizardryItems.wizard_hat_necromancy);
registerItemModel(WizardryItems.wizard_robe_necromancy);
registerItemModel(WizardryItems.wizard_leggings_necromancy);
registerItemModel(WizardryItems.wizard_boots_necromancy);
registerItemModel(WizardryItems.wizard_hat_earth);
registerItemModel(WizardryItems.wizard_robe_earth);
registerItemModel(WizardryItems.wizard_leggings_earth);
registerItemModel(WizardryItems.wizard_boots_earth);
registerItemModel(WizardryItems.wizard_hat_sorcery);
registerItemModel(WizardryItems.wizard_robe_sorcery);
registerItemModel(WizardryItems.wizard_leggings_sorcery);
registerItemModel(WizardryItems.wizard_boots_sorcery);
registerItemModel(WizardryItems.wizard_hat_healing);
registerItemModel(WizardryItems.wizard_robe_healing);
registerItemModel(WizardryItems.wizard_leggings_healing);
registerItemModel(WizardryItems.wizard_boots_healing);
registerItemModel(WizardryItems.spectral_helmet);
registerItemModel(WizardryItems.spectral_chestplate);
registerItemModel(WizardryItems.spectral_leggings);
registerItemModel(WizardryItems.spectral_boots);
registerItemModel(WizardryItems.smoke_bomb);
registerItemModel(WizardryItems.identification_scroll);
}
// Moved from the proxies
/**
* Registers an item model, using the item's registry name as the model name (this convention makes it easier to
* keep track of everything). Variant defaults to "normal". Registers the model for metadata 0 automatically, plus
* all the other metadata values that the item can take, as defined in
* {@link Item#getSubItems(Item, net.minecraft.creativetab.CreativeTabs, java.util.List)}. The passed in item
* <b>must</b> allow null to be passed in for the creative tab parameter in the aforementioned method, or a
* {@link NullPointerException} will result.
*/
private static void registerItemModel(Item item){
if(item.getHasSubtypes()){
NonNullList<ItemStack> items = NonNullList.create();
item.getSubItems(item.getCreativeTab(), items); // Client-only method, but we're client-side so this is OK.
for(ItemStack stack : items){
ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(),
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
}
// Changing the last parameter from null to "inventory" fixed the item/block model weirdness. No idea why!
ModelLoader.setCustomModelResourceLocation(item, 0,
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
/**
* Registers an item model for the given metadata, using the item's registry name as the model name (this convention
* makes it easier to keep track of everything). This is intended for registering additional metadata values which
* aren't displayed in the creative menu, for example the wildcard spell book used in wizard trades.
*/
private static void registerItemModel(Item item, int metadata, String variant){
ModelLoader.setCustomModelResourceLocation(item, metadata,
new ModelResourceLocation(item.getRegistryName(), variant));
}
}
@@ -0,0 +1,343 @@
package electroblob.wizardry.client.model;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockCrystal;
import electroblob.wizardry.block.BlockPedestal;
import electroblob.wizardry.block.BlockRunestone;
import electroblob.wizardry.item.IMultiTexturedItem;
import electroblob.wizardry.item.ItemBlockMultiTexturedElemental;
import electroblob.wizardry.item.ItemCrystal;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.client.renderer.block.statemap.StateMap;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.oredict.OreDictionary;
/**
* Class responsible for registering all of wizardry's item and block models.
*
* @author Electroblob
* @since Wizardry 2.1
*/
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryModels {
private WizardryModels(){} // No instances!
@SubscribeEvent
public static void register(ModelRegistryEvent event){
// ItemBlocks
registerItemModel(Item.getItemFromBlock(WizardryBlocks.arcane_workbench));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_ore));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_flower));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.transportation_stone));
ModelLoader.setCustomStateMapper(WizardryBlocks.crystal_block, new StateMap.Builder()
.withName(BlockCrystal.ELEMENT).withSuffix("_crystal_block").build());
// Yay unchecked casting! But we know it's always ok here, and it makes everything much neater.
ItemBlockMultiTexturedElemental crystalBlockItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.crystal_block);
registerMultiTexturedModel(crystalBlockItem);
ModelLoader.setCustomStateMapper(WizardryBlocks.runestone, new StateMap.Builder()
.withName(BlockRunestone.ELEMENT).withSuffix("_runestone").build());
ItemBlockMultiTexturedElemental runestoneItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.runestone);
registerMultiTexturedModel(runestoneItem);
ModelLoader.setCustomStateMapper(WizardryBlocks.runestone_pedestal, new StateMap.Builder()
.withName(BlockPedestal.ELEMENT).ignore(BlockPedestal.NATURAL).withSuffix("_runestone_pedestal").build()); // Don't care about NATURAL property
ItemBlockMultiTexturedElemental pedestalItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.runestone_pedestal);
registerMultiTexturedModel(pedestalItem);
// Items
registerMultiTexturedModel((ItemCrystal)WizardryItems.magic_crystal);
registerItemModel(WizardryItems.magic_wand);
registerItemModel(WizardryItems.apprentice_wand);
registerItemModel(WizardryItems.advanced_wand);
registerItemModel(WizardryItems.master_wand);
registerItemModel(WizardryItems.spell_book);
// Wildcard registered for wizard trades.
registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal");
registerItemModel(WizardryItems.arcane_tome);
registerItemModel(WizardryItems.wizard_handbook);
registerItemModel(WizardryItems.novice_fire_wand);
registerItemModel(WizardryItems.novice_ice_wand);
registerItemModel(WizardryItems.novice_lightning_wand);
registerItemModel(WizardryItems.novice_necromancy_wand);
registerItemModel(WizardryItems.novice_earth_wand);
registerItemModel(WizardryItems.novice_sorcery_wand);
registerItemModel(WizardryItems.novice_healing_wand);
registerItemModel(WizardryItems.apprentice_fire_wand);
registerItemModel(WizardryItems.apprentice_ice_wand);
registerItemModel(WizardryItems.apprentice_lightning_wand);
registerItemModel(WizardryItems.apprentice_necromancy_wand);
registerItemModel(WizardryItems.apprentice_earth_wand);
registerItemModel(WizardryItems.apprentice_sorcery_wand);
registerItemModel(WizardryItems.apprentice_healing_wand);
registerItemModel(WizardryItems.advanced_fire_wand);
registerItemModel(WizardryItems.advanced_ice_wand);
registerItemModel(WizardryItems.advanced_lightning_wand);
registerItemModel(WizardryItems.advanced_necromancy_wand);
registerItemModel(WizardryItems.advanced_earth_wand);
registerItemModel(WizardryItems.advanced_sorcery_wand);
registerItemModel(WizardryItems.advanced_healing_wand);
registerItemModel(WizardryItems.master_fire_wand);
registerItemModel(WizardryItems.master_ice_wand);
registerItemModel(WizardryItems.master_lightning_wand);
registerItemModel(WizardryItems.master_necromancy_wand);
registerItemModel(WizardryItems.master_earth_wand);
registerItemModel(WizardryItems.master_sorcery_wand);
registerItemModel(WizardryItems.master_healing_wand);
registerItemModel(WizardryItems.spectral_sword);
registerItemModel(WizardryItems.spectral_pickaxe);
registerItemModel(WizardryItems.spectral_bow);
registerItemModel(WizardryItems.small_mana_flask);
registerItemModel(WizardryItems.medium_mana_flask);
registerItemModel(WizardryItems.large_mana_flask);
registerItemModel(WizardryItems.crystal_shard);
registerItemModel(WizardryItems.grand_crystal);
registerItemModel(WizardryItems.astral_diamond);
registerItemModel(WizardryItems.purifying_elixir);
registerItemModel(WizardryItems.storage_upgrade);
registerItemModel(WizardryItems.siphon_upgrade);
registerItemModel(WizardryItems.condenser_upgrade);
registerItemModel(WizardryItems.range_upgrade);
registerItemModel(WizardryItems.duration_upgrade);
registerItemModel(WizardryItems.cooldown_upgrade);
registerItemModel(WizardryItems.blast_upgrade);
registerItemModel(WizardryItems.attunement_upgrade);
registerItemModel(WizardryItems.melee_upgrade);
registerItemModel(WizardryItems.flaming_axe);
registerItemModel(WizardryItems.frost_axe);
registerItemModel(WizardryItems.firebomb);
registerItemModel(WizardryItems.poison_bomb);
registerItemModel(WizardryItems.smoke_bomb);
registerItemModel(WizardryItems.spark_bomb);
registerItemModel(WizardryItems.blank_scroll);
registerItemModel(WizardryItems.scroll);
registerItemModel(WizardryItems.identification_scroll);
registerItemModel(WizardryItems.armour_upgrade);
registerItemModel(WizardryItems.magic_silk);
registerItemModel(WizardryItems.wizard_hat);
registerItemModel(WizardryItems.wizard_robe);
registerItemModel(WizardryItems.wizard_leggings);
registerItemModel(WizardryItems.wizard_boots);
registerItemModel(WizardryItems.wizard_hat_fire);
registerItemModel(WizardryItems.wizard_robe_fire);
registerItemModel(WizardryItems.wizard_leggings_fire);
registerItemModel(WizardryItems.wizard_boots_fire);
registerItemModel(WizardryItems.wizard_hat_ice);
registerItemModel(WizardryItems.wizard_robe_ice);
registerItemModel(WizardryItems.wizard_leggings_ice);
registerItemModel(WizardryItems.wizard_boots_ice);
registerItemModel(WizardryItems.wizard_hat_lightning);
registerItemModel(WizardryItems.wizard_robe_lightning);
registerItemModel(WizardryItems.wizard_leggings_lightning);
registerItemModel(WizardryItems.wizard_boots_lightning);
registerItemModel(WizardryItems.wizard_hat_necromancy);
registerItemModel(WizardryItems.wizard_robe_necromancy);
registerItemModel(WizardryItems.wizard_leggings_necromancy);
registerItemModel(WizardryItems.wizard_boots_necromancy);
registerItemModel(WizardryItems.wizard_hat_earth);
registerItemModel(WizardryItems.wizard_robe_earth);
registerItemModel(WizardryItems.wizard_leggings_earth);
registerItemModel(WizardryItems.wizard_boots_earth);
registerItemModel(WizardryItems.wizard_hat_sorcery);
registerItemModel(WizardryItems.wizard_robe_sorcery);
registerItemModel(WizardryItems.wizard_leggings_sorcery);
registerItemModel(WizardryItems.wizard_boots_sorcery);
registerItemModel(WizardryItems.wizard_hat_healing);
registerItemModel(WizardryItems.wizard_robe_healing);
registerItemModel(WizardryItems.wizard_leggings_healing);
registerItemModel(WizardryItems.wizard_boots_healing);
registerItemModel(WizardryItems.spectral_helmet);
registerItemModel(WizardryItems.spectral_chestplate);
registerItemModel(WizardryItems.spectral_leggings);
registerItemModel(WizardryItems.spectral_boots);
registerItemModel(WizardryItems.lightning_hammer);
registerItemModel(WizardryItems.ring_condensing);
registerItemModel(WizardryItems.ring_siphoning);
registerItemModel(WizardryItems.ring_battlemage);
registerItemModel(WizardryItems.ring_combustion);
registerItemModel(WizardryItems.ring_fire_melee);
registerItemModel(WizardryItems.ring_fire_biome);
registerItemModel(WizardryItems.ring_disintegration);
registerItemModel(WizardryItems.ring_ice_melee);
registerItemModel(WizardryItems.ring_ice_biome);
registerItemModel(WizardryItems.ring_arcane_frost);
registerItemModel(WizardryItems.ring_shattering);
registerItemModel(WizardryItems.ring_lightning_melee);
registerItemModel(WizardryItems.ring_storm);
registerItemModel(WizardryItems.ring_seeking);
registerItemModel(WizardryItems.ring_hammer);
registerItemModel(WizardryItems.ring_soulbinding);
registerItemModel(WizardryItems.ring_leeching);
registerItemModel(WizardryItems.ring_necromancy_melee);
registerItemModel(WizardryItems.ring_mind_control);
registerItemModel(WizardryItems.ring_poison);
registerItemModel(WizardryItems.ring_earth_melee);
registerItemModel(WizardryItems.ring_earth_biome);
registerItemModel(WizardryItems.ring_full_moon);
registerItemModel(WizardryItems.ring_extraction);
registerItemModel(WizardryItems.ring_mana_return);
registerItemModel(WizardryItems.ring_blockwrangler);
registerItemModel(WizardryItems.ring_conjurer);
registerItemModel(WizardryItems.ring_defender);
registerItemModel(WizardryItems.ring_paladin);
registerItemModel(WizardryItems.ring_interdiction);
registerItemModel(WizardryItems.amulet_arcane_defence);
registerItemModel(WizardryItems.amulet_warding);
registerItemModel(WizardryItems.amulet_wisdom);
registerItemModel(WizardryItems.amulet_fire_protection);
registerItemModel(WizardryItems.amulet_fire_cloaking);
registerItemModel(WizardryItems.amulet_ice_immunity);
registerItemModel(WizardryItems.amulet_ice_protection);
registerItemModel(WizardryItems.amulet_potential);
registerItemModel(WizardryItems.amulet_channeling);
registerItemModel(WizardryItems.amulet_lich);
registerItemModel(WizardryItems.amulet_wither_immunity);
registerItemModel(WizardryItems.amulet_glide);
registerItemModel(WizardryItems.amulet_banishing);
registerItemModel(WizardryItems.amulet_anchoring);
registerItemModel(WizardryItems.amulet_recovery);
registerItemModel(WizardryItems.amulet_transience);
registerItemModel(WizardryItems.amulet_resurrection);
registerItemModel(WizardryItems.amulet_auto_shield);
registerItemModel(WizardryItems.charm_haggler);
registerItemModel(WizardryItems.charm_experience_tome);
registerItemModel(WizardryItems.charm_auto_smelt);
registerItemModel(WizardryItems.charm_lava_walking);
registerItemModel(WizardryItems.charm_storm);
registerItemModel(WizardryItems.charm_minion_health);
registerItemModel(WizardryItems.charm_minion_variants);
registerItemModel(WizardryItems.charm_flight);
registerItemModel(WizardryItems.charm_growth);
registerItemModel(WizardryItems.charm_abseiling);
registerItemModel(WizardryItems.charm_silk_touch);
registerItemModel(WizardryItems.charm_stop_time);
registerItemModel(WizardryItems.charm_light);
registerItemModel(WizardryItems.charm_transportation);
registerItemModel(WizardryItems.charm_feeding);
}
@SubscribeEvent
public static void bake(ModelBakeEvent event){
// MMMmmmm I love the smell of freshly-baked models...
// This stuff is the boilerplate for making runestone overlay render with full brightness
// See https://www.minecraftforge.net/forum/topic/66005-how-do-i-make-a-tileentityspecialrenderer-solved-with-ibakedmodel/
// As usual the Forge documentation is just a description of each class and not an explanation of how to use them
// I had to work out where this goes from the refined storage repo linked in the above thread, which is mixed in
// with a more extensive registration system (which is super neat, but it's overkill for our purposes)
// https://github.com/raoulvdberge/refinedstorage/blob/13d6e7f2b92f41b5009187aa2cbde50dbc72082f/src/main/java/com/raoulvdberge/refinedstorage/proxy/ProxyClient.java#L59
for(ModelResourceLocation location : event.getModelRegistry().getKeys()){
if(location.getNamespace().equals(Wizardry.MODID)){
if(location.getPath().contains("runestone") || location.getPath().contains("runestone_pedestal")){
IBakedModel original = event.getModelRegistry().getObject(location);
event.getModelRegistry().putObject(location, new BakedModelGlowingOverlay(original, "overlay"));
}
}
}
}
// Moved from the proxies
/**
* Registers an item model, using the item's registry name as the model name (this convention makes it easier to
* keep track of everything). Variant defaults to "normal". Registers the model for metadata 0 automatically, plus
* all the other metadata values that the item can take, as defined in
* {@link Item#getSubItems(CreativeTabs, NonNullList)}. The creative tab supplied
* to the aforementioned method will be whichever one the item is in.
*/
private static void registerItemModel(Item item){
if(item.getHasSubtypes()){
NonNullList<ItemStack> items = NonNullList.create();
item.getSubItems(item.getCreativeTab(), items); // Client-only method, but we're client-side so this is OK.
for(ItemStack stack : items){
ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(),
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
}
// Changing the last parameter from null to "inventory" fixed the item/block model weirdness. No idea why!
ModelLoader.setCustomModelResourceLocation(item, 0,
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
/**
* Registers an item model, using the itemstack-sensitive {@link IMultiTexturedItem#getModelName(ItemStack)} as the
* model name. This allows items to change their texture based on metadata/NBT. Variant defaults to "normal". Registers the
* model for metadata 0 automatically, plus all the other metadata values that the item can take, as defined in
* {@link Item#getSubItems(CreativeTabs, NonNullList)}. The creative tab supplied
* to the aforementioned method will be whichever one the item is in.
*/
private static <T extends Item & IMultiTexturedItem> void registerMultiTexturedModel(T item){
if(item.getHasSubtypes()){
NonNullList<ItemStack> items = NonNullList.create();
item.getSubItems(item.getCreativeTab(), items);
for(ItemStack stack : items){
ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(),
new ModelResourceLocation(item.getModelName(stack), "inventory"));
}
}
}
/**
* Registers an item model for the given metadata, using the item's registry name as the model name (this convention
* makes it easier to keep track of everything). This is intended for registering additional metadata values which
* aren't displayed in the creative menu, for example the wildcard spell book used in wizard trades.
*/
private static void registerItemModel(Item item, int metadata, String variant){
ModelLoader.setCustomModelResourceLocation(item, metadata,
new ModelResourceLocation(item.getRegistryName(), variant));
}
}
@@ -0,0 +1,99 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleBeam extends ParticleTargeted {
/** Half the width of the outermost layer. */
private static final float THICKNESS = 0.1f;
public ParticleBeam(World world, double x, double y, double z){
super(world, x, y, z); // Does not have a texture!
this.setRBGColorF(1, 1, 1);
this.setMaxAge(0);
this.particleScale = 1;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public int getFXLayer(){
return 3;
}
@Override
protected void draw(Tessellator tessellator, double length, float partialTicks){
float scale = this.particleScale;
if(this.particleMaxAge > 0){
float ageFraction = (particleAge + partialTicks - 1)/particleMaxAge;
// Squaring this makes it look smoother than a linear shrinking effect
scale = this.particleScale * (1 - ageFraction*ageFraction);
}
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.disableTexture2D();
GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
for(int layer=0; layer<3; layer++){
drawSegment(tessellator, layer, 0, 0, 0, 0, 0, length, THICKNESS * scale);
}
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
GlStateManager.disableBlend();
}
/** Draws the given layer of a segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with the given thickness. */
private void drawSegment(Tessellator tessellator, int layer, double x1, double y1, double z1, double x2, double y2, double z2, float thickness){
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR);
switch(layer){
case 0:
drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, 0.25f*thickness, 1, 1, 1, 1);
break;
case 1:
drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, 0.6f*thickness, (particleRed + 1)/2, (particleGreen + 1)/2,
(particleBlue + 1)/2, 0.65f);
break;
case 2:
drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, thickness, particleRed, particleGreen, particleBlue, 0.3f);
break;
}
tessellator.draw();
}
/** Draws a single box for one segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with given width and colour. */
private void drawShearedBox(BufferBuilder buffer, double x1, double y1, double z1, double x2, double y2, double z2, float width, float r, float g, float b, float a){
buffer.pos(x1-width, y1-width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2-width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1-width, y1+width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2+width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1+width, y1+width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2+width, y2+width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1+width, y1-width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2+width, y2-width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1-width, y1-width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2-width, z2).color(r, g, b, a).endVertex();
}
}
@@ -1,74 +0,0 @@
package electroblob.wizardry.client.particle;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleBlizzard extends ParticleSnow {
private double angle;
private double radius;
private double speed;
public ParticleBlizzard(World world, int maxAge, double originX, double originZ, double radius, double yPos){
super(world, 0, 0, 0, 0, 0, 0, maxAge);
this.angle = this.rand.nextDouble() * Math.PI * 2;
double x = originX - Math.cos(angle) * radius;
double z = originZ + radius * Math.sin(angle);
this.radius = radius;
this.setPosition(x, yPos, z);
this.prevPosX = x;
this.prevPosY = yPos;
this.prevPosZ = z;
if(rand.nextBoolean()){
speed = rand.nextDouble() * 2 + 1;
}else{
speed = rand.nextDouble() * -2 - 1;
}
this.multipleParticleScaleBy(1.5f);
}
@Override
public void init(){
super.init();
this.fullBrightness = true;
}
// @Override
// public void renderParticle(VertexBuffer buffer, Entity entity, float partialTicks, float rotationX, float
// rotationZ, float rotationYZ, float rotationXY, float rotationXZ){
// if(this.particleAge < this.particleMaxAge / 3 || (this.particleAge + this.particleMaxAge) / 3 % 2 == 0){
// super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ);
// }
// }
@Override
public void onUpdate(){
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
if(this.particleAge++ >= this.particleMaxAge){
this.setExpired();
}
// This is in radians per tick...
double omega = Math.signum(speed) * ((Math.PI * 2) / 20 - speed / (20 * radius));
// v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi.
this.angle += omega;
this.motionY -= 0.04D * (double)this.particleGravity;
this.motionZ = radius * omega * Math.cos(angle);
this.motionX = radius * omega * Math.sin(angle);
this.move(motionX, motionY, motionZ);
if(this.particleAge > this.particleMaxAge / 2){
this.setAlphaF(
1.0F - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge);
}
}
}
@@ -0,0 +1,136 @@
package electroblob.wizardry.client.particle;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
//@SideOnly(Side.CLIENT)
public class ParticleBuff extends ParticleWizardry {
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/particle/buff.png");
private final boolean mirror;
public ParticleBuff(World world, double x, double y, double z){
super(world, x, y, z);
this.setVelocity(0, 0.162, 0); // Approximately what it was before
this.mirror = random.nextBoolean();
this.setMaxAge(15);
this.setGravity(false);
this.canCollide = false;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public void onUpdate(){
super.onUpdate();
if(this.particleAge > this.particleMaxAge/2) this.particleAlpha = 2f - 2f*(float)this.particleAge/(float)this.particleMaxAge;
}
/* There are 4 layers of particles, specified as 0-3 by the method below. - Layer 0 causes the normal particles.png
* to be bound to the render engine for normal particles. - Layer 1 causes the block textures to be bound to the
* render engine for digging fx and falling fx. - Layer 2 causes the item textures to be bound to the render engine
* for tool breaking fx, snowballpoofs, slime particles, etc. - Layer 3 is not used in vanilla minecraft and was
* presumably added by forge for exactly this reason. This means no texture is bound by vanilla minecraft, meaning
* you are free to do as you wish without possibly overwriting vanilla particles. Mod particles won't be overwritten
* anyway since they bind their own textures. It is of course important to bind the texture every time you render a
* custom particle, but I don't see how you could do it any other way, since you don't have access to
* EffectRenderer. */
@Override
public int getFXLayer(){
// This can only be 0-3 or it will cause an ArrayIndexOutOfBoundsException in EffectRenderer.
return 3;
}
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ,
float rotationYZ, float rotationXY, float rotationXZ){
// Copied from ParticleWizardry, needs to be here since we're not calling super
updateEntityLinking(partialTicks);
GlStateManager.pushMatrix();
GlStateManager.pushAttrib();
GlStateManager.enableBlend();
GlStateManager.disableAlpha();
GlStateManager.disableCull();
GlStateManager.disableLighting();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE);
// Makes the particle colour add to the colour of the texture pixels, rather than the default multiplying
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
// Does the texture translation wrapping thing (the cool stuff)
GlStateManager.matrixMode(GL11.GL_TEXTURE);
GlStateManager.loadIdentity();
GlStateManager.translate((this.particleAge + partialTicks)/(float)this.particleMaxAge * -2, 0, 0);
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
RenderHelper.disableStandardItemLighting();
Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE);
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ);
// Increases from 0 to 1 in steps of 0.125 evenly throughout the particle's lifetime
float f = 0.875f - 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f);
float g = f + 0.125f;
float hrepeat = 1;
float scale = 0.6f;
float yScale = 0.7f * scale;
float dx = mirror ? -scale : scale;
float dz = scale;
buffer.pos(x-dx, y-yScale, z-dz).tex(0, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y+yScale, z-dz).tex(0, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y-yScale, z-dz).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y+yScale, z-dz).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y-yScale, z+dz).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y+yScale, z+dz).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y-yScale, z+dz).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y+yScale, z+dz).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y-yScale, z-dz).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y+yScale, z-dz).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
Tessellator.getInstance().draw();
// Undoes the texture transformations
GlStateManager.matrixMode(GL11.GL_TEXTURE);
GlStateManager.loadIdentity();
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
GlStateManager.disableBlend();
GlStateManager.enableAlpha();
GlStateManager.enableCull();
GlStateManager.enableLighting();
// Reverses the colour addition change from before
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
GlStateManager.popAttrib();
GlStateManager.popMatrix();
}
}
@@ -1,219 +0,0 @@
package electroblob.wizardry.client.particle;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/**
* Abstract superclass for all particles that use custom textures. This is intended to centralise as much code as
* possible; all subclasses need to do is to define the texture to use, how the frames are arranged (and which to
* choose), and any properties like gravity and collisions.
*
* @author Electroblob
* @since Wizardry 1.2
*/
@SideOnly(Side.CLIENT)
public abstract class ParticleCustomTexture extends Particle {
/** True if the particle always renders at full brightness. Defaults to false. */
protected boolean fullBrightness = false;
public ParticleCustomTexture(World world, double x, double y, double z, double vx, double vy, double vz){
super(world, x, y, z, vx, vy, vz);
this.motionX = vx;
this.motionY = vy;
this.motionZ = vz;
this.init();
}
public ParticleCustomTexture(World world, double x, double y, double z, double vx, double vy, double vz,
int maxAge){
this(world, x, y, z, vx, vy, vz);
this.particleMaxAge = maxAge;
}
/**
* Called from both constructors to set constants, avoiding duplicate code. Common fields to set here include:
* particleScale, particleGravity, canCollide, fullBrightness and setting the texture index.
*/
public abstract void init();
/**
* Returns a ResourceLocation for the particle's texture sheet. Do not create a new ResourceLocation in this method,
* only return a constant.
*/
public abstract ResourceLocation getTexture();
/** Returns how many 'frames' there are in the x direction on the texture. */
protected abstract int getXFrames();
/** Returns how many 'frames' there are in the y direction on the texture. */
protected abstract int getYFrames();
/* There are 4 layers of particles, specified as 0-3 by the method below. - Layer 0 causes the normal particles.png
* to be bound to the render engine for normal particles. - Layer 1 causes the block textures to be bound to the
* render engine for digging fx and falling fx. - Layer 2 causes the item textures to be bound to the render engine
* for tool breaking fx, snowballpoofs, slime particles, etc. - Layer 3 is not used in vanilla minecraft and was
* presumably added by forge for exactly this reason. This means no texture is bound by vanilla minecraft, meaning
* you are free to do as you wish without possibly overwriting vanilla particles. Mod particles won't be overwritten
* anyway since they bind their own textures. It is of course important to bind the texture every time you render a
* custom particle, but I don't see how you could do it any other way, since you don't have access to
* EffectRenderer. */
@Override
public int getFXLayer(){
// This can only be 0-3 or it will cause an ArrayIndexOutOfBoundsException in EffectRenderer.
return 3;
}
@Override
public void setParticleTextureIndex(int index){
this.particleTextureIndexX = index % getXFrames();
this.particleTextureIndexY = index / getYFrames();
}
// Overridden to fix the bug with vanilla that makes particles frictionless. (y != y... seriously, Mojang?)
// TESTME: Probably no longer necessary.
// @Override
// public void move(double x, double y, double z){
//
// double d0 = y;
//
// if (this.canCollide)
// {
// List<AxisAlignedBB> list = this.world.getCollisionBoxes((Entity)null, this.getBoundingBox().addCoord(x, y, z));
//
// for (AxisAlignedBB axisalignedbb : list)
// {
// y = axisalignedbb.calculateYOffset(this.getBoundingBox(), y);
// }
//
// this.setBoundingBox(this.getBoundingBox().offset(0.0D, y, 0.0D));
//
// for (AxisAlignedBB axisalignedbb1 : list)
// {
// x = axisalignedbb1.calculateXOffset(this.getBoundingBox(), x);
// }
//
// this.setBoundingBox(this.getBoundingBox().offset(x, 0.0D, 0.0D));
//
// for (AxisAlignedBB axisalignedbb2 : list)
// {
// z = axisalignedbb2.calculateZOffset(this.getBoundingBox(), z);
// }
//
// this.setBoundingBox(this.getBoundingBox().offset(0.0D, 0.0D, z));
// }
// else
// {
// this.setBoundingBox(this.getBoundingBox().offset(x, y, z));
// }
//
// this.resetPositionToBB();
// this.onGround = d0 != y && d0 < 0.0D;
//
// /* Can never be true! - But this doesn't seem to make any difference anyway.
// if (x != x)
// {
// this.motionX = 0.0D;
// }
//
// if (z != z)
// {
// this.motionZ = 0.0D;
// }
// */
// }
// Overridden to bind the new texture. I think this can be done with TextureAtlasSprite, but this works as it is
// so I'm not changing it for the time being.
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ,
float rotationYZ, float rotationXY, float rotationXZ){
GlStateManager.pushMatrix();
GlStateManager.pushAttrib();
this.applyGLStateChanges();
// This stuff does the shading. It vanilla does this later on for each point, but this also seems to work.
int brightness = this.getBrightnessForRender(partialTicks);
int lightmapX = brightness % 65536;
int lightmapY = brightness / 65536;
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)lightmapX / 1.0F,
(float)lightmapY / 1.0F);
RenderHelper.disableStandardItemLighting();
Minecraft.getMinecraft().getTextureManager().bindTexture(getTexture());
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
float u1 = (float)this.particleTextureIndexX / (float)getXFrames();
float u2 = u1 + 1.0f / getXFrames();
float v1 = (float)this.particleTextureIndexY / (float)getYFrames();
float v2 = v1 + 1.0f / getYFrames();
float scale = 0.1F * this.particleScale;
// I'm pretty sure these were always static.
Particle.interpPosX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * (double)partialTicks;
Particle.interpPosY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * (double)partialTicks;
Particle.interpPosZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * (double)partialTicks;
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ);
buffer.pos((double)(x - rotationX * scale - rotationXY * scale), (double)(y - rotationZ * scale),
(double)(z - rotationYZ * scale - rotationXZ * scale)).tex(u2, v2)
.color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos((double)(x - rotationX * scale + rotationXY * scale), (double)(y + rotationZ * scale),
(double)(z - rotationYZ * scale + rotationXZ * scale)).tex(u2, v1)
.color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos((double)(x + rotationX * scale + rotationXY * scale), (double)(y + rotationZ * scale),
(double)(z + rotationYZ * scale + rotationXZ * scale)).tex(u1, v1)
.color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos((double)(x + rotationX * scale - rotationXY * scale), (double)(y - rotationZ * scale),
(double)(z + rotationYZ * scale - rotationXZ * scale)).tex(u1, v2)
.color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
;
Tessellator.getInstance().draw();
this.undoGLStateChanges();
GlStateManager.popAttrib();
GlStateManager.popMatrix();
}
/**
* Override to add any GL state changes, like blending. Does nothing by default. <b>State changes should be done
* using GLStateManager, not using GL11 directly</b> (as is the case with all rendering code now).
*/
public void applyGLStateChanges(){
}
/**
* Override to undo any GL state changes, like blending. Does nothing by default. <b>State changes should be done
* using GLStateManager, not using GL11 directly</b> (as is the case with all rendering code now).
*/
public void undoGLStateChanges(){
}
@Override
public int getBrightnessForRender(float partialTick){
return fullBrightness ? 15728880 : super.getBrightnessForRender(partialTick);
}
}
@@ -1,27 +1,20 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleDarkMagic extends Particle {
//@SideOnly(Side.CLIENT)
public class ParticleDarkMagic extends ParticleWizardry {
/** Base spell texture index */
private int baseSpellTextureIndex = 128;
public ParticleDarkMagic(World par1World, double par2, double par4, double par6, double par8, double par10,
double par12, float r, float g, float b){
super(par1World, par2, par4, par6, par8, par10, par12);
public ParticleDarkMagic(World world, double x, double y, double z){
super(world, x, y, z);
this.motionY *= 0.20000000298023224D;
this.particleRed = r;
this.particleGreen = g;
this.particleBlue = b;
this.setRBGColorF(1, 1, 1);
this.particleScale *= 0.75F;
this.particleMaxAge = (int)(8.0D / (Math.random() * 0.8D + 0.2D));
this.canCollide = true;
@@ -43,9 +36,7 @@ public class ParticleDarkMagic extends Particle {
super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ);
}
/**
* Called to update the entity's position/logic.
*/
@Override
public void onUpdate(){
this.prevPosX = this.posX;
this.prevPosY = this.posY;
@@ -1,48 +1,31 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.particle.Particle;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleDust extends Particle {
//@SideOnly(Side.CLIENT)
public class ParticleDust extends ParticleWizardry {
private final boolean shaded;
public ParticleDust(World par1World, double x, double y, double z, double par8, double par10, double par12, float r,
float g, float b, boolean shaded){
super(par1World, x, y, z, par8, par10, par12);
this.particleRed = r;
this.particleGreen = g;
this.particleBlue = b;
public ParticleDust(World world, double x, double y, double z){
super(world, x, y, z);
this.setParticleTextureIndex(0);
this.setSize(0.01F, 0.01F);
// Defaults
this.particleScale *= this.rand.nextFloat() + 0.2F;
this.motionX = par8;
this.motionY = par10;
this.motionZ = par12;
this.particleMaxAge = (int)(16.0D / (Math.random() * 0.8D + 0.2D));
this.shaded = shaded;
this.setRBGColorF(1, 1, 1);
}
/**
* Called to update the entity's position/logic.
*/
@Override
public void onUpdate(){
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
// this.moveEntity(this.motionX, this.motionY, this.motionZ);
this.move(this.motionX, this.motionY, this.motionZ);
if(this.particleMaxAge-- <= 0){
this.setExpired();
}
}
@Override
public int getBrightnessForRender(float par1){
return shaded ? super.getBrightnessForRender(par1) : 15728880;
}
/* @Override public float getBrightness(float par1) { return shaded ? super.getBrightness(par1) : 1.0F; } */
}
@@ -0,0 +1,49 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
/**
* Copied from ParticleFirework.Overlay; for some reason that class has no public constructors, plus I want to change the
* scale and a few other things
* @author Electroblob
* @since Wizardry 4.2.0
*/
public class ParticleFlash extends ParticleWizardry {
public ParticleFlash(World world, double x, double y, double z){
super(world, x, y, z);
this.setRBGColorF(1, 1, 1);
this.particleScale = 0.6f; // 7.1f is the value used in fireworks
this.particleMaxAge = 6;
}
@Override
public boolean shouldDisableDepth(){
return true; // Well this fixes everything... let's hope it doesn't cause any side-effects!
}
@Override
public void drawParticle(BufferBuilder buffer, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){
float f4 = particleScale * MathHelper.sin(((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * (float)Math.PI);
this.setAlphaF(0.6F - ((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * 0.5F);
float f5 = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
float f6 = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
float f7 = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ);
int i = this.getBrightnessForRender(partialTicks);
int j = i >> 16 & 65535;
int k = i & 65535;
buffer.pos((double)(f5 - rotationX * f4 - rotationXY * f4), (double)(f6 - rotationZ * f4), (double)(f7 - rotationYZ * f4 - rotationXZ * f4)).tex(0.5D, 0.375D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex();
buffer.pos((double)(f5 - rotationX * f4 + rotationXY * f4), (double)(f6 + rotationZ * f4), (double)(f7 - rotationYZ * f4 + rotationXZ * f4)).tex(0.5D, 0.125D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex();
buffer.pos((double)(f5 + rotationX * f4 + rotationXY * f4), (double)(f6 + rotationZ * f4), (double)(f7 + rotationYZ * f4 + rotationXZ * f4)).tex(0.25D, 0.125D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex();
buffer.pos((double)(f5 + rotationX * f4 - rotationXY * f4), (double)(f6 - rotationZ * f4), (double)(f7 + rotationYZ * f4 - rotationXZ * f4)).tex(0.25D, 0.375D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex();
}
@Override
public int getBrightnessForRender(float partialTicks){
return 15728880;
}
}
@@ -1,49 +0,0 @@
package electroblob.wizardry.client.particle;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.particle.Particle;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleGiantBubble extends Particle {
/**
* The name used to identify this particle. Uses the mod id to avoid any possible conflicts (Not that there would be
* any, but I may as well.)
*/
public static final String NAME = Wizardry.MODID + "magicbubble";
public ParticleGiantBubble(World par1World, double par2, double par4, double par6, double par8, double par10,
double par12){
super(par1World, par2, par4, par6, par8, par10, par12);
this.particleRed = 1.0F;
this.particleGreen = 1.0F;
this.particleBlue = 1.0F;
this.setParticleTextureIndex(32);
this.setSize(0.02F, 0.02F);
this.particleScale *= this.rand.nextFloat() * 0.6F + 0.2F;
this.motionX = par8 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F);
this.motionY = par10 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F);
this.motionZ = par12 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F);
this.particleMaxAge = (int)(8.0D / (Math.random() * 0.8D + 0.2D));
}
/**
* Called to update the entity's position/logic.
*/
public void onUpdate(){
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
this.motionY += 0.002D;
this.move(this.motionX, this.motionY, this.motionZ);
this.motionX *= 0.8500000238418579D;
this.motionY *= 0.8500000238418579D;
this.motionZ *= 0.8500000238418579D;
if(this.particleMaxAge-- <= 0){
this.setExpired();
}
}
}
@@ -1,46 +1,35 @@
package electroblob.wizardry.client.particle;
import electroblob.wizardry.Wizardry;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleIce extends ParticleCustomTexture {
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleIce extends ParticleWizardry {
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID,
"textures/particle/ice_particles.png");
public ParticleIce(World world, double x, double y, double z, double vx, double vy, double vz){
super(world, x, y, z, vx, vy, vz);
}
public ParticleIce(World world, double x, double y, double z, double vx, double vy, double vz, int maxAge){
super(world, x, y, z, vx, vy, vz, maxAge);
}
@Override
public void init(){
this.setParticleTextureIndex(rand.nextInt(8));
this.particleScale *= 0.75f;
this.particleGravity = 1;
private static final ResourceLocation[] TEXTURES = generateTextures("ice", 8);
public ParticleIce(World world, double x, double y, double z){
super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]);
this.canCollide = true;
this.fullBrightness = true;
// Defaults
this.setRBGColorF(1, 1, 1);
this.particleScale *= 0.75f;
this.setGravity(true);
this.shaded = false;
}
@Override
public ResourceLocation getTexture(){
return TEXTURE;
}
@Override
protected int getXFrames(){
return 4;
}
@Override
protected int getYFrames(){
return 4;
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation texture : TEXTURES){
event.getMap().registerSprite(texture);
}
}
}
@@ -1,45 +1,46 @@
package electroblob.wizardry.client.particle;
import electroblob.wizardry.Wizardry;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleLeaf extends ParticleCustomTexture {
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleLeaf extends ParticleWizardry {
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID,
"textures/particle/leaf_particles.png");
private static final ResourceLocation[] TEXTURES = generateTextures("leaf", 16);
public ParticleLeaf(World world, double x, double y, double z, double vx, double vy, double vz){
super(world, x, y, z, vx, vy, vz);
}
public ParticleLeaf(World world, double x, double y, double z, double vx, double vy, double vz, int maxAge){
super(world, x, y, z, vx, vy, vz, maxAge);
}
@Override
public void init(){
this.setParticleTextureIndex(rand.nextInt(16));
public ParticleLeaf(World world, double x, double y, double z){
super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]);
this.setVelocity(0, -0.03, 0);
this.setMaxAge(10 + rand.nextInt(5));
this.particleScale *= 1.4f;
this.particleGravity = 0;
this.canCollide = true;
// Produces a variety of browns and greens
this.setRBGColorF(0.1f + 0.3f * random.nextFloat(), 0.5f + 0.3f * random.nextFloat(), 0.1f);
}
@Override
public ResourceLocation getTexture(){
return TEXTURE;
public void onUpdate(){
super.onUpdate();
// Fading
if(this.particleAge > this.particleMaxAge / 2){
this.setAlphaF(1 - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge);
}
}
@Override
protected int getXFrames(){
return 4;
}
@Override
protected int getYFrames(){
return 4;
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation texture : TEXTURES){
event.getMap().registerSprite(texture);
}
}
}
@@ -0,0 +1,165 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleLightning extends ParticleTargeted {
/** Half the width of the outermost layer. */
private static final float THICKNESS = 0.04f;
/** Maximum length of a segment. */
private static final double MAX_SEGMENT_LENGTH = 0.6;
/** Minimum length of a segment. */
private static final double MIN_SEGMENT_LENGTH = 0.2;
/** Maximum deviation (in x or y, as drawn before transformations) from the centreline. */
private static final double VERTEX_JITTER = 0.15;
/** Maximum number of segments a fork can have before ending. */
private static final int MAX_FORK_SEGMENTS = 3;
/** Probability (as a fraction) that a vertex will have a fork. */
private static final float FORK_CHANCE = 0.3f;
/** Number of ticks to wait before the arc changes shape again. */
private static final int UPDATE_PERIOD = 1;
public ParticleLightning(World world, double x, double y, double z){
super(world, x, y, z); // Does not have a texture!
seed = this.rand.nextLong();
this.setRBGColorF(0.2f, 0.6f, 1); // Default blue colour
this.setMaxAge(3);
this.particleScale = 1;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public int getFXLayer(){
return 3;
}
@Override
protected void draw(Tessellator tessellator, double length, float partialTicks){
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.disableTexture2D();
GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// The direction of the arc drawn by the tessellator is always along the z axis and is rotated to the
// correct orientation, that way there isn't a ton of trigonometry and the code is way neater.
boolean freeEnd = this.target == null;
int numberOfSegments = (int)Math.round(length/MAX_SEGMENT_LENGTH); // Number of segments
for(int layer=0; layer<3; layer++){
double px=0, py=0, pz=0;
// Creates a random from the arc's seed field + the number of ticks it has existed/the update period.
// By using a seed, we can ensure the vertex positions and forks are identical a) for each layer, even
// though they are rendered sequentially, and b) across many frames (and ticks, if updateTime > 1).
random.setSeed(this.seed + this.particleAge/UPDATE_PERIOD);
// numberOfSegments-1 because the last segment is handled separately.
for(int i=0; i<numberOfSegments-1; i++){
double px2 = (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale;
double py2 = (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale;
double pz2 = pz + length/(double)numberOfSegments; // For now they are all the same length
drawSegment(tessellator, layer, px, py, pz, px2, py2, pz2, THICKNESS*particleScale);
// Forks
if(random.nextFloat() < FORK_CHANCE){
double px3=px, py3=py, pz3=pz;
for(int j=0; j<random.nextInt(MAX_FORK_SEGMENTS-1)+1; j++){
// Forks set their centreline to the x/y coordinates of the vertex they originate from
double px4 = px3 + (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale;
double py4 = py3 + (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale;
double pz4 = pz3 + MIN_SEGMENT_LENGTH + random.nextDouble()*(MAX_SEGMENT_LENGTH - MIN_SEGMENT_LENGTH);
drawSegment(tessellator, layer, px3, py3, pz3, px4, py4, pz4, THICKNESS*0.8f*particleScale);
// Forks of forks
if(random.nextFloat() < FORK_CHANCE){
double px5 = px3 + (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale;
double py5 = py3 + (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale;
double pz5 = pz3 + MIN_SEGMENT_LENGTH + random.nextDouble()*(MAX_SEGMENT_LENGTH - MIN_SEGMENT_LENGTH);
drawSegment(tessellator, layer, px3, py3, pz3, px5, py5, pz5, THICKNESS*0.6f*particleScale);
}
px3 = px4;
py3 = py4;
pz3 = pz4;
}
}
px = px2;
py = py2;
pz = pz2;
}
// Last segment has a specific end position and cannot fork.
double px2 = freeEnd ? (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale : 0;
double py2 = freeEnd ? (random.nextDouble()*2-1)*VERTEX_JITTER*particleScale : 0;
drawSegment(tessellator, layer, px, py, pz, px2, py2, length, THICKNESS*particleScale);
}
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
GlStateManager.disableBlend();
}
/** Draws the given layer of a segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with the given thickness. */
private void drawSegment(Tessellator tessellator, int layer, double x1, double y1, double z1, double x2, double y2, double z2, float thickness){
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR);
switch(layer){
case 0:
drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, 0.25f*thickness, 1, 1, 1, 1);
break;
case 1:
drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, 0.6f*thickness, (particleRed + 1)/2, (particleGreen + 1)/2,
(particleBlue + 1)/2, 0.65f);
break;
case 2:
drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, thickness, particleRed, particleGreen, particleBlue, 0.3f);
break;
}
tessellator.draw();
}
/** Draws a single box for one segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with given width and colour. */
private void drawShearedBox(BufferBuilder buffer, double x1, double y1, double z1, double x2, double y2, double z2, float width, float r, float g, float b, float a){
buffer.pos(x1-width, y1-width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2-width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1-width, y1+width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2+width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1+width, y1+width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2+width, y2+width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1+width, y1-width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2+width, y2-width, z2).color(r, g, b, a).endVertex();
buffer.pos(x1-width, y1-width, z1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2-width, z2).color(r, g, b, a).endVertex();
}
}
@@ -0,0 +1,42 @@
package electroblob.wizardry.client.particle;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleLightningPulse extends ParticleWizardry {
private static final ResourceLocation[] TEXTURES = generateTextures("lightning_pulse", 8);
public ParticleLightningPulse(World world, double x, double y, double z){
super(world, x, y, z, TEXTURES);
this.particleScale = 32f;
this.setRBGColorF(1, 1, 1);
this.shaded = false;
this.canCollide = false;
this.setMaxAge(7); // Lifetime defaults to 7 (and is very unlikely to be changed)
// Faces up by default
this.pitch = 90;
this.yaw = 0;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation texture : TEXTURES){
event.getMap().registerSprite(texture);
}
}
}
@@ -0,0 +1,35 @@
package electroblob.wizardry.client.particle;
import net.minecraft.world.World;
//@SideOnly(Side.CLIENT)
public class ParticleMagicBubble extends ParticleWizardry {
public ParticleMagicBubble(World world, double x, double y, double z){
super(world, x, y, z);
this.setRBGColorF(1, 1, 1);
this.setParticleTextureIndex(32);
this.setSize(0.02F, 0.02F);
this.particleScale *= this.rand.nextFloat() * 0.6F + 0.2F;
// this.motionX = par8 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F);
// this.motionY = par10 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F);
// this.motionZ = par12 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F);
this.particleMaxAge = (int)(8.0D / (Math.random() * 0.8D + 0.2D));
}
@Override
public void onUpdate(){
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
this.motionY += 0.002D;
this.move(this.motionX, this.motionY, this.motionZ);
this.motionX *= 0.8500000238418579D;
this.motionY *= 0.8500000238418579D;
this.motionZ *= 0.8500000238418579D;
if(this.particleMaxAge-- <= 0){
this.setExpired();
}
}
}
@@ -1,46 +1,36 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticleMagicFlame extends Particle {
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleMagicFlame extends ParticleWizardry {
/** The scale of the flame particle */
private float flameScale;
// 4 different animation strips, 8 frames in each strip
private static final ResourceLocation[][] TEXTURES = generateTextures("flame", 4, 8);
public ParticleMagicFlame(World par1World, double par2, double par4, double par6, double par8, double par10,
double par12, int maxAge, float scale){
super(par1World, par2, par4, par6, par8, par10, par12);
this.motionX = this.motionX * 0.009999999776482582D + par8;
this.motionY = this.motionY * 0.009999999776482582D + par10;
this.motionZ = this.motionZ * 0.009999999776482582D + par12;
this.flameScale = scale;
this.particleRed = this.particleGreen = this.particleBlue = 1.0F;
if(maxAge == 0){
this.particleMaxAge = (int)(2.0D / (Math.random() * 0.8D + 0.2D));
}else{
this.particleMaxAge = maxAge;
public ParticleMagicFlame(World world, double x, double y, double z){
super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]);
this.setRBGColorF(1, 1, 1);
this.particleAlpha = 1;
this.particleMaxAge = 12 + rand.nextInt(4);
this.shaded = false;
this.canCollide = true;
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation[] array : TEXTURES){
for(ResourceLocation texture : array){
event.getMap().registerSprite(texture);
}
}
// IDEA: Make the particles for ray spells collide properly and not spawn on the other side of stuff.
this.canCollide = false;
this.setParticleTextureIndex(48);
}
@Override
public void renderParticle(BufferBuilder buffer, Entity entity, float partialTicks, float rotationX, float rotationZ,
float rotationYZ, float rotationXY, float rotationXZ){
float f6 = ((float)this.particleAge + partialTicks) / (float)this.particleMaxAge;
this.particleScale = this.flameScale * (1.0F - f6 * f6 * 0.5F);
super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ);
}
@Override
public int getBrightnessForRender(float par1){
return 256;
}
}
@@ -1,69 +1,47 @@
package electroblob.wizardry.client.particle;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.spell.Clairvoyance;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ParticlePath extends ParticleCustomTexture {
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticlePath extends ParticleWizardry {
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID,
"textures/particle/path_particles.png");
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "particle/path");
private final double originX, originY, originZ;
public ParticlePath(World world, double x, double y, double z, double vx, double vy, double vz, float r, float g,
float b){
super(world, x, y, z, vx, vy, vz);
this.setRBGColorF(r, g, b);
public ParticlePath(World world, double x, double y, double z){
super(world, x, y, z, TEXTURE); // This particle only has 1 texture
this.originX = x;
this.originY = y;
this.originZ = z;
}
public ParticlePath(World world, double x, double y, double z, double vx, double vy, double vz, float r, float g,
float b, int maxAge){
super(world, x, y, z, vx, vy, vz, maxAge);
this.setRBGColorF(r, g, b);
this.originX = x;
this.originY = y;
this.originZ = z;
}
@Override
public void init(){
this.setParticleTextureIndex(0);
// Set to a constant to remove the randomness from Particle.
this.particleScale = 1.25f;
this.particleGravity = 0;
this.fullBrightness = true;
this.shaded = false;
this.canCollide = false;
this.setRBGColorF(1, 1, 1);
}
@Override
public ResourceLocation getTexture(){
return TEXTURE;
}
@Override
protected int getXFrames(){
return 1;
}
@Override
protected int getYFrames(){
return 1;
public boolean shouldDisableDepth(){
return true;
}
@Override
public void onUpdate(){
super.onUpdate();
this.prevPosX = this.posX;
this.prevPosY = this.posY;
@@ -89,19 +67,10 @@ public class ParticlePath extends ParticleCustomTexture {
}
}
@Override
public void applyGLStateChanges(){
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// TESTME: Are these two actually necessary?
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
}
@Override
public void undoGLStateChanges(){
GlStateManager.disableBlend();
GlStateManager.enableLighting();
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
event.getMap().registerSprite(TEXTURE);
}
}

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