Compare commits
1050 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89a0a900cc | |||
| 391a24db3f | |||
| 4b546af642 | |||
| ee8aabbf97 | |||
| 45994e2b94 | |||
| 13bdc69da8 | |||
| c0184fde8e | |||
| cbd297e59d | |||
| c6cf7c950e | |||
| fe5d80e017 | |||
| e985a8f15b | |||
| 615767f758 | |||
| 65a24b2295 | |||
| 94c2afe67a | |||
| 9bbca37ecd | |||
| 58241a0f5c | |||
| a960a01ca1 | |||
| 67d8ee773c | |||
| c6f2675278 | |||
| 2380be230e | |||
| 833276b7b3 | |||
| 9b32835720 | |||
| 61634a9cc3 | |||
| 408a35db8d | |||
| 9695a85131 | |||
| d027cf4f2a | |||
| c2f8bfc5b4 | |||
| 486425a62c | |||
| f9681d4c66 | |||
| cd0671dc59 | |||
| 7ebca16948 | |||
| e8cbdde518 | |||
| 7ca6f05151 | |||
| f4f17e90e5 | |||
| 3ea48efb1d | |||
| ffc3533688 | |||
| bd309855a2 | |||
| 1473c7e9b7 | |||
| 15cc7b6d18 | |||
| 1c89c5f779 | |||
| ca7f1cc8ca | |||
| 8fd1de84e4 | |||
| 95d9168b6b | |||
| c1db827132 | |||
| d10b78ab46 | |||
| 2d70523f43 | |||
| 42fb7ad3df | |||
| e90d0950d4 | |||
| 888d4190d1 | |||
| 57696b2538 | |||
| 1440537f6b | |||
| a16ff88c8d | |||
| 041de12ea7 | |||
| 5e36f66c4e | |||
| a6a5a6104d | |||
| 6f7b225ce8 | |||
| 81a85883d2 | |||
| 3f06d2fea4 | |||
| 26b390268a | |||
| 486d55f349 | |||
| 5948090d63 | |||
| 32340191de | |||
| 0b09875bb3 | |||
| 078a36d304 | |||
| 3ef4ec522b | |||
| cd6fa03990 | |||
| bba9fb68e6 | |||
| dcb7038833 | |||
| f8a058259d | |||
| 1696d5daeb | |||
| ac69af012a | |||
| 692546a232 | |||
| b0478b627f | |||
| 2cdca4b990 | |||
| 4c0b2adef4 | |||
| 191dd078b8 | |||
| 2de7807bec | |||
| 9d4f1dd370 | |||
| 6f769150b1 | |||
| 155dc0b621 | |||
| 07acea53e3 | |||
| 1fcb3a2b1b | |||
| 16eabc8deb | |||
| e0efd8d407 | |||
| 969f0b96fd | |||
| 8a9d88c9ac | |||
| 3b55728c75 | |||
| 240bffa7ed | |||
| c313236eec | |||
| 21f7928b9c | |||
| 1304c11381 | |||
| 79932e15ab | |||
| 25ee166e41 | |||
| 299510e663 | |||
| d3d76cae16 | |||
| 0e815c676d | |||
| f81ad670ed | |||
| 5b3aebaba1 | |||
| 3ef1afce2f | |||
| a3c15d673b | |||
| d8f1c5f77c | |||
| 1ddcdafe50 | |||
| 3c35eaaf54 | |||
| 094ce5a427 | |||
| e19d324dca | |||
| 58f6d55b0d | |||
| 32e92b4b04 | |||
| 24a06e0bac | |||
| ae9da5b7c1 | |||
| 2e39a8bfc1 | |||
| e3130ca936 | |||
| 378e19a614 | |||
| e2fd978d8b | |||
| 8f8ce7a453 | |||
| ddfffd3d31 | |||
| d23c15476a | |||
| 55bb40a684 | |||
| c056c490da | |||
| c483591ea8 | |||
| 4c3dfb3bc9 | |||
| 0010fafc13 | |||
| f4cf98089c | |||
| 8e3a33b084 | |||
| b1478de6fe | |||
| 1d247e2d56 | |||
| e8292f1e96 | |||
| bc0b712e2b | |||
| 47135f00d7 | |||
| c615ad365c | |||
| 39c3989796 | |||
| e808ab2b1f | |||
| a25f942722 | |||
| 14e481a71d | |||
| ac226f1444 | |||
| 741d918d9b | |||
| f504505bf5 | |||
| fb010d3726 | |||
| c64731e7a9 | |||
| d3837a6185 | |||
| 80cf77ecc6 | |||
| 3d3806c20b | |||
| d952e9b255 | |||
| 33c8f08adf | |||
| 315f2e4b02 | |||
| d3df59df73 | |||
| cfd3e796e1 | |||
| d97e843f7b | |||
| 15a153fe11 | |||
| d00b7279ee | |||
| 0142cc05bc | |||
| 23621b8045 | |||
| 7f2978bc7a | |||
| 312364ba77 | |||
| 3b4ce68606 | |||
| 164e9dbe36 | |||
| 2a21eb4392 | |||
| 97061f7a32 | |||
| 7f84f3daef | |||
| 9f3e09498d | |||
| 55bcd92860 | |||
| 1030a2a4f3 | |||
| bc407187e1 | |||
| 62c7b6b7bb | |||
| 35ac115e8b | |||
| f99f8c271e | |||
| 21dabc9cfc | |||
| 77d9bfc772 | |||
| 29617451d8 | |||
| e530695f54 | |||
| 1f4de65356 | |||
| 9dc186abf4 | |||
| 086d9f6af6 | |||
| 7e658d74b5 | |||
| 2bb242e6fb | |||
| d85441867e | |||
| 38aa80d60a | |||
| 5e35e53eb3 | |||
| f41effd44f | |||
| 17fad8b33c | |||
| 1c96c88082 | |||
| 2b884d0aea | |||
| 89345d4fbc | |||
| cf8de05d1b | |||
| f9432c7c05 | |||
| d6f30d6519 | |||
| eb864338b4 | |||
| bf13491987 | |||
| 0dd26de905 | |||
| 795bdcd427 | |||
| 6f57d2366e | |||
| 8b86bb1b31 | |||
| 0db343c959 | |||
| f5cd740497 | |||
| b6c67ddf34 | |||
| c51b08c827 | |||
| 970894ea21 | |||
| d5d933b4e6 | |||
| 7e32263946 | |||
| 543cc22c44 | |||
| c86cd88a57 | |||
| ac5b3466e7 | |||
| 3cdd564228 | |||
| a5fb3c6ac5 | |||
| 3f36f933ff | |||
| 22b284063b | |||
| f32bcae8d3 | |||
| c05f4001fd | |||
| a72ac33768 | |||
| c53c9b1793 | |||
| c56128a7e2 | |||
| b4e9d86d1d | |||
| aac931f6b2 | |||
| ed9e9110e5 | |||
| c4194e2b76 | |||
| 84236093c8 | |||
| 77f698397a | |||
| b7329299f9 | |||
| 127723a1e7 | |||
| 4a7b8a2bb9 | |||
| 2f91573284 | |||
| f49ef7594d | |||
| 0022de499d | |||
| 359da9a688 | |||
| 1a636c8c40 | |||
| 2c95793293 | |||
| 93d2100546 | |||
| eeb408c68c | |||
| d5ed705f22 | |||
| adf07a7c98 | |||
| ea4e07b2c9 | |||
| ac7e2fda94 | |||
| 8690d406f3 | |||
| ebcfd3963d | |||
| ea9c9edff1 | |||
| 2e49f1d06b | |||
| 0981768c72 | |||
| 70a55d1175 | |||
| 7ae041595a | |||
| ac14c8f931 | |||
| e147ce5d37 | |||
| 472727552e | |||
| 373ab0c3dc | |||
| 5bca261582 | |||
| 99f71874c0 | |||
| 1c8011ff74 | |||
| f9cd90bd49 | |||
| 988596075c | |||
| 2b27729062 | |||
| dac40ecf98 | |||
| c48b4ea0fe | |||
| 7380da7b5e | |||
| 815bcf176c | |||
| ba487bea41 | |||
| 6cdff07180 | |||
| 87705f7e4b | |||
| c90ec248cd | |||
| 0c046e8c29 | |||
| b7a503e5c4 | |||
| aaf5f1ff71 | |||
| a5dd5ce9df | |||
| b08e5e1d96 | |||
| b6bda04a7a | |||
| 1cf5808f4f | |||
| 7401a8e3b4 | |||
| c3ca12edb9 | |||
| 9d8b0b3a90 | |||
| 0a75e34f3f | |||
| 2f0fd9fd7b | |||
| 0d7d36c0b5 | |||
| a321e4ef66 | |||
| 041f54352c | |||
| 09cc0a5c34 | |||
| 1e4117fcab | |||
| cfb537dc0f | |||
| e5e01dca88 | |||
| eba219bcce | |||
| 8632d71ed6 | |||
| 5e0d096ec0 | |||
| 7c463c9eb9 | |||
| cd83bbb67d | |||
| 476b7a6a45 | |||
| e4290598d5 | |||
| 3343385a7c | |||
| a831c2ada3 | |||
| a945201cec | |||
| 2e3df272ec | |||
| ded4caca2f | |||
| a12fc6e344 | |||
| fa0875e63b | |||
| 05d33ec58e | |||
| 7013cffeaa | |||
| a1a5af034d | |||
| 9aac386ec9 | |||
| 651eb8834b | |||
| 48221582c3 | |||
| e04e1e70af | |||
| a78a4147db | |||
| 657a014cae | |||
| de7226f792 | |||
| 22a125d720 | |||
| 8ae6bc9102 | |||
| 3f19e89b91 | |||
| 399c3017ad | |||
| 183d5a2416 | |||
| ddd67c6034 | |||
| 202552a967 | |||
| 9d3439b25c | |||
| b6ccee010b | |||
| 02ab4a9014 | |||
| 429f74393e | |||
| a547b3a384 | |||
| 8e2b367e94 | |||
| ea6d338d71 | |||
| c2396bcd5e | |||
| 4e0b63f06b | |||
| 323b1e1799 | |||
| 148f527504 | |||
| 6a8951ab39 | |||
| b76374fb58 | |||
| c435ca5fa7 | |||
| c9735e9a98 | |||
| 0400e9276d | |||
| 632790fabd | |||
| b61bbda668 | |||
| c2e99e2ce6 | |||
| cfca8f1fdd | |||
| b0d44738c7 | |||
| 8088e21ba4 | |||
| 68dad2a148 | |||
| a25b642164 | |||
| b37fa47211 | |||
| aea65b3753 | |||
| 3a68ba8927 | |||
| 97c21b5057 | |||
| 5be939a39f | |||
| 080022127c | |||
| eb017c01a9 | |||
| 1ea6355a9e | |||
| f6f766c5b6 | |||
| d8bf437214 | |||
| 5dc3ebed4f | |||
| 43690ace29 | |||
| e6696a1758 | |||
| 93b0aa948f | |||
| 439faf425f | |||
| 33c5f79ed7 | |||
| 0ca86eb01b | |||
| d69b736a72 | |||
| ac4bb8bc11 | |||
| fbf8e7155c | |||
| f2de40e096 | |||
| c6a3f090cd | |||
| 46a467de46 | |||
| 62b06f8fc6 | |||
| f18f1d20f3 | |||
| a88aa57ef0 | |||
| ba1ed773af | |||
| 76f2145072 | |||
| ba856346a9 | |||
| 39e33848aa | |||
| dfe6ecadac | |||
| e9093b9360 | |||
| cbc778bff7 | |||
| b65709fca4 | |||
| d45bdbc3d0 | |||
| 50f0072c7a | |||
| c443e0d39c | |||
| 14483629e5 | |||
| 3a5a97c6b9 | |||
| 9936388600 | |||
| 6aacebcc74 | |||
| c842fef2f7 | |||
| a87eca3bd8 | |||
| daf174ec3f | |||
| 7afcdce21e | |||
| 3bc2ff2853 | |||
| f7f9190227 | |||
| bfd7f6e5a5 | |||
| 8e8e249426 | |||
| 99d0d115c7 | |||
| 798c015944 | |||
| 732621f37c | |||
| 238b9d04bb | |||
| 57e96458d5 | |||
| 9dbdd2b830 | |||
| d60238a400 | |||
| 7319651ac4 | |||
| 36a228489b | |||
| 27e9b1d6f2 | |||
| 4bf8aff2e6 | |||
| 1c0e046236 | |||
| a65c56eb41 | |||
| 00f958a71e | |||
| 8aadf5a446 | |||
| a344011486 | |||
| a17a20fe8b | |||
| ee56aa50be | |||
| b4b32d52e3 | |||
| 526dbaf396 | |||
| 737f896070 | |||
| fb56190f6f | |||
| c4ac1d7e6c | |||
| 7d650ebbc3 | |||
| 72971b5f16 | |||
| 937c791196 | |||
| 3348938d46 | |||
| f95819cf37 | |||
| 9563d730bd | |||
| 5be8c8ed36 | |||
| 38b6643d1d | |||
| 038ead0d55 | |||
| a4d342caa1 | |||
| c00c04acd2 | |||
| 7772f2e97f | |||
| 99a9524ee7 | |||
| 840ef3b3a5 | |||
| 009321d905 | |||
| 1a094e1970 | |||
| 6df0d2dc6e | |||
| 5011246e79 | |||
| c8e07cf857 | |||
| a54f00875f | |||
| ce92d564f4 | |||
| afabcbde49 | |||
| 88d0780d69 | |||
| 33d4652758 | |||
| 09967ac256 | |||
| aa84b2b8cd | |||
| a8f1ac38fa | |||
| d11c3fcc2f | |||
| 92b54edb4a | |||
| e54453e6c6 | |||
| c7e7bb5c25 | |||
| 596a17453d | |||
| 70a3dee588 | |||
| b4fc50ad71 | |||
| 930ad77db5 | |||
| 20de12792f | |||
| 6eaccb9976 | |||
| a877275916 | |||
| e4e8e0a444 | |||
| ef222e66f2 | |||
| 31eed1cb48 | |||
| 0cd61b758e | |||
| 4ef54cf8cb | |||
| 5066439fef | |||
| dd12d2ebcb | |||
| cfdda5716d | |||
| a33715ad51 | |||
| 05aac02966 | |||
| 1af0186886 | |||
| 3e0dbb9755 | |||
| d9f7d94115 | |||
| 94f4e680af | |||
| e17b5d2300 | |||
| 601f8a97fa | |||
| e6b7f25932 | |||
| 6c08b21f7f | |||
| d22c530f79 | |||
| ddcaffc1b1 | |||
| 6696be59a4 | |||
| e1df468d93 | |||
| 5a57e8341a | |||
| dd137c68f6 | |||
| 1275f1d31f | |||
| 27d53b4e93 | |||
| 46691e9934 | |||
| 30f2a99a2c | |||
| 650344a0c8 | |||
| ebde514c98 | |||
| 4739d9f9fc | |||
| ef39757229 | |||
| e09f9313f9 | |||
| 94a17a63c1 | |||
| 594c546f11 | |||
| cda9947e17 | |||
| 48a350b63a | |||
| 28da6810d7 | |||
| f5ce186a7d | |||
| 04bebe5910 | |||
| fe5060a2c0 | |||
| 8de990fdde | |||
| 8a92773a17 | |||
| 9f8b2fee1e | |||
| 9205f082f1 | |||
| ff4c17d1c1 | |||
| 34a4609af2 | |||
| 25475632d9 | |||
| bfc23ce6f7 | |||
| a97abd26d0 | |||
| 11821ff14d | |||
| 5c34b379d1 | |||
| c4d4c67302 | |||
| c04ee35fc3 | |||
| 09b52a4586 | |||
| 1f9f5a1cc5 | |||
| be250e218e | |||
| cf38a16f49 | |||
| a31b1c79e3 | |||
| ad520eff77 | |||
| 0354f99bd2 | |||
| 301ec86c27 | |||
| ee96ff3ddd | |||
| 53f545caad | |||
| 755933486c | |||
| 5d8a90167c | |||
| f89c71f314 | |||
| 5ad4cc0610 | |||
| f0752317e6 | |||
| ed63704184 | |||
| 3316c8e5cb | |||
| 0a2368650a | |||
| b9958ce16f | |||
| 8cbe5dd602 | |||
| f1cb7dbbbe | |||
| 5f8477a4ad | |||
| d4c2397a68 | |||
| a7b608a06e | |||
| 50ad3655c9 | |||
| abc598a722 | |||
| b23136d568 | |||
| 8291373427 | |||
| 618df1c668 | |||
| cdc326e36c | |||
| 4c024f9e46 | |||
| d93b9a6765 | |||
| 7b1283c418 | |||
| 6882fb4519 | |||
| 57f4704e18 | |||
| 50ca70b0db | |||
| 821361f9bf | |||
| 2c05e52912 | |||
| 8301a24afe | |||
| 945c789482 | |||
| 13ba19c1ff | |||
| 9bc920073b | |||
| df526dc76f | |||
| 702398dd34 | |||
| 1e5add914e | |||
| 4980beea87 | |||
| 91b2ee1a13 | |||
| 937617d7af | |||
| 94751a6fd5 | |||
| 01ab50cb27 | |||
| d0bcbc8ed8 | |||
| b660b02e0e | |||
| 153ea74966 | |||
| c81830efec | |||
| 6ec2e716cb | |||
| 96e7c5885b | |||
| 41d575c9c9 | |||
| 7f31fc6259 | |||
| 817fd26dcf | |||
| aecc8b0889 | |||
| b45c202ac5 | |||
| 16ad8543d8 | |||
| e985bc6f69 | |||
| 8682fcbe91 | |||
| e8ca773557 | |||
| c622158818 | |||
| 7237840a2a | |||
| 6254020024 | |||
| 1d161e613c | |||
| e8c1b4d581 | |||
| b5f61205eb | |||
| 6f0636da9f | |||
| b3fd39c42c | |||
| 40ed431ee2 | |||
| 15d3d9e70b | |||
| 8ad6b5590b | |||
| 1c2c267e1a | |||
| e45bfb9248 | |||
| 5df98d5b95 | |||
| a4fd88f21d | |||
| 793ba0eb05 | |||
| 531700378f | |||
| 4875d5a970 | |||
| 3ff6be4cb0 | |||
| c418757042 | |||
| e272d4f999 | |||
| c50b344041 | |||
| bd11c9c759 | |||
| f7b678e291 | |||
| 6cabe7ffd4 | |||
| f973099fd3 | |||
| 1833ed9c56 | |||
| d56cf37f2b | |||
| c5b5172481 | |||
| 938d0df95c | |||
| 525b7e13dd | |||
| a67eb2381d | |||
| 0d48606b51 | |||
| 3c0b65c485 | |||
| 771118afde | |||
| 29b9e0de8b | |||
| f26f537f50 | |||
| 91c9a5392b | |||
| 6ae9df1204 | |||
| d46600ac86 | |||
| 004782a1e4 | |||
| b2b48cd8a1 | |||
| 2277f6b278 | |||
| ba59d9786f | |||
| bdf5aa5b64 | |||
| a384a57622 | |||
| 586840aa53 | |||
| 1ab674136b | |||
| 71302b3219 | |||
| c161a74d73 | |||
| df1f737539 | |||
| 64e7e3844e | |||
| 51a3d2a63e | |||
| 53a053bfff | |||
| 220bf15cfb | |||
| 885e58c85c | |||
| 7c2e786d0b | |||
| a7ee0ad847 | |||
| 033d90ed3e | |||
| 849066de8a | |||
| eb65b7d7b7 | |||
| 97499eb523 | |||
| 02480db158 | |||
| e35ee9d126 | |||
| 004c4c09e3 | |||
| 52214f81cf | |||
| 516d7948ee | |||
| a490168fa2 | |||
| 306137a026 | |||
| 0a5527f3d4 | |||
| 4dbe24c8ea | |||
| 5477725b7e | |||
| 81bba5e7e0 | |||
| 9c29142e6a | |||
| 3dc3a3a378 | |||
| 3151d7d626 | |||
| d68f968632 | |||
| 74d9198522 | |||
| 0b56d47022 | |||
| 587ab1338e | |||
| 4816c48aa6 | |||
| 2456589fa7 | |||
| d7849a94b1 | |||
| cfb9f4fe25 | |||
| a269cf339b | |||
| 1c8e4f86ee | |||
| f2835c245f | |||
| 462512ad30 | |||
| db5a7dcfde | |||
| 17bba5dea8 | |||
| fbdb5cd844 | |||
| 393fb1c1b0 | |||
| 08e89b1aef | |||
| 1f25d95b1b | |||
| 1f3a71797e | |||
| a821ef2aef | |||
| 6845279c49 | |||
| 79b745e005 | |||
| 6fff0e31d1 | |||
| 11efe56437 | |||
| 7f5786c28c | |||
| 85b49b330a | |||
| d797e72445 | |||
| 5f21b565cd | |||
| 9ed06c9a34 | |||
| 50f315666f | |||
| 62bdbc81e4 | |||
| d9c3d15b9b | |||
| bb6bb78163 | |||
| 3f80c56163 | |||
| 3a571fd239 | |||
| 4fb5452dc4 | |||
| 652f1d2620 | |||
| 275f514810 | |||
| 7cf09e76f8 | |||
| 61080134fd | |||
| 563bed3b25 | |||
| 75740c388f | |||
| 5d96f4d700 | |||
| df8fd21c0b | |||
| 046ecb1e41 | |||
| f3dd7bccd8 | |||
| d6b64b3576 | |||
| 22c00c47ff | |||
| 6d0e65c2b0 | |||
| 0a6fd452c1 | |||
| 28a8a4ff2a | |||
| 71c09cf16d | |||
| 794085660e | |||
| 3d89186308 | |||
| a0bd7b64d7 | |||
| 4c633c98ff | |||
| 45ff70c207 | |||
| 0328f41564 | |||
| 9ab4684aef | |||
| e64cf9a6af | |||
| f13f220564 | |||
| 311b5750df | |||
| e54aee332d | |||
| e4bf09f15a | |||
| aa166814ea | |||
| d37ab6072f | |||
| ba83cb7985 | |||
| 3052e56d03 | |||
| b682740751 | |||
| 38429cb697 | |||
| dc7b6790b8 | |||
| 7a247df3e1 | |||
| de2087c0e8 | |||
| f5da195605 | |||
| 6cd6411c25 | |||
| 40e77728ad | |||
| d50ff5729f | |||
| 76b4f2f72f | |||
| a608375433 | |||
| c3f8ba245d | |||
| 34b3fe93bb | |||
| 996ec9a7f7 | |||
| 2b38ca9a89 | |||
| 67788a2bd8 | |||
| 58fcaa8af2 | |||
| 24e89c8b8a | |||
| 1293b89905 | |||
| 807fc61541 | |||
| 9741c751b3 | |||
| d3003ae473 | |||
| 9fde24f341 | |||
| 452eb17b5f | |||
| 36963aac30 | |||
| f161cdfe7c | |||
| 455c5a5e07 | |||
| 9852f61d0f | |||
| a2c8cde4c4 | |||
| 497d28eafd | |||
| f3f3377c4d | |||
| 4c81c61644 | |||
| 31a6ab6db0 | |||
| 69f2aedef7 | |||
| e8818a4db5 | |||
| 470a7e82bc | |||
| 6439c233f1 | |||
| c931d42d62 | |||
| 5b497e107a | |||
| 324094f418 | |||
| 9ade6442b2 | |||
| ffea49bc40 | |||
| 2b89a890d0 | |||
| 3a67388ff3 | |||
| 3468bbb073 | |||
| 963178c609 | |||
| cfdb049368 | |||
| 058d83fd00 | |||
| c7ccc37dca | |||
| 74345d0c3b | |||
| 907b94c74f | |||
| 2c6b397be6 | |||
| eddcb2402d | |||
| 0301f3c300 | |||
| 7a8cfb73cd | |||
| c7c6f964bf | |||
| 91df7cfd72 | |||
| 0ed9ee64c4 | |||
| f82d2871d5 | |||
| 01cea3df06 | |||
| 591cfb5857 | |||
| bc2037f74e | |||
| 6aa3b21190 | |||
| b262b6edc0 | |||
| b7be05669b | |||
| 071e299d48 | |||
| 1e885d70c3 | |||
| e04a43e56d | |||
| 378796cdda | |||
| 48c37d1c17 | |||
| fe8f9fc5cd | |||
| ced6ab7e8d | |||
| 378e02eb6c | |||
| 4677d9ff30 | |||
| 0541b2171b | |||
| dc59a14e82 | |||
| 1a13afb6e3 | |||
| 03dd76c18a | |||
| 9426a96df3 | |||
| 382faf59c9 | |||
| cab68e23ac | |||
| 60a6c5124e | |||
| 028b753be6 | |||
| d020668e0c | |||
| 656a78962e | |||
| 5a2f5aa295 | |||
| 5e37a2322c | |||
| 3ca9f200fd | |||
| 37e3a842f5 | |||
| 4e8e2c951b | |||
| 43b66df22a | |||
| b192a431d7 | |||
| 3be98ab612 | |||
| ed950322ff | |||
| 4cde8c60bc | |||
| 9924e10f13 | |||
| eeb0640525 | |||
| 693c11ada0 | |||
| 038fd7225f | |||
| 999e32f37c | |||
| 8c2ac5adbb | |||
| 9c1e2e08c3 | |||
| e7f7960540 | |||
| a46bad4497 | |||
| 06d02ad45d | |||
| c39a213e62 | |||
| 2caca39a22 | |||
| 8d46191ff8 | |||
| fb7af9328d | |||
| 0991baf964 | |||
| bdefecbe9b | |||
| eef48f319a | |||
| 3a788c867c | |||
| 1ba723ade1 | |||
| c06105cd00 | |||
| 97c72685bb | |||
| 23fccaac9c | |||
| 74b6bdba0f | |||
| d69e3a253d | |||
| 744c7a2331 | |||
| c355cb034c | |||
| 5e321cc4cf | |||
| fbecce0543 | |||
| a6c24e9cb6 | |||
| 14a624db3f | |||
| 16a4c8231f | |||
| 931c99617c | |||
| 037f340a03 | |||
| b6cc417641 | |||
| 6565a0734a | |||
| 0aac70c5fb | |||
| 8551dc407b | |||
| 1eec7921ce | |||
| 18920a24c5 | |||
| e0c6f0f00f | |||
| a4f6391fac | |||
| a570bd57d0 | |||
| f1ff179b98 | |||
| 6d09acc78e | |||
| 960d97de49 | |||
| ebe64b06e3 | |||
| 99887f95e2 | |||
| 2e5a9fce3e | |||
| 2be6230977 | |||
| 518d69a2e2 | |||
| 93bc9b1b89 | |||
| 413d21d1d8 | |||
| 78fc00c0cc | |||
| a1d2d6a16e | |||
| d6492ee3f1 | |||
| 831f68d8d9 | |||
| f86ea2334d | |||
| f37812be3e | |||
| d8f3ca2d01 | |||
| b262f711e2 | |||
| 4da63d6735 | |||
| be80d4863c | |||
| 62c696cd13 | |||
| f58dfea4bf | |||
| a924545457 | |||
| 290d420e6b | |||
| e4da941e40 | |||
| 8e95e830a3 | |||
| 7d0334fc5a | |||
| b352105dd4 | |||
| 05e6b53970 | |||
| e76dc4a873 | |||
| 4d164c15ad | |||
| 6b30c7923e | |||
| 97a23d0e39 | |||
| 44d1d4dbb7 | |||
| 22cb4d7f56 | |||
| 54abdb6746 | |||
| 414b773da6 | |||
| 2680d02304 | |||
| f564fdce60 | |||
| 3bdee06738 | |||
| 31673cf247 | |||
| f6f36a30fd | |||
| 2db96cf18d | |||
| b8ba7d1ea4 | |||
| b33037fd1b | |||
| a378fe86f5 | |||
| 5dfa58bf27 | |||
| 740bfb1a8f | |||
| 03e8682260 | |||
| 4ca89b2dca | |||
| 212fbe51df | |||
| eb2abdfbe9 | |||
| f8cfc59415 | |||
| 13569058ae | |||
| 78189ed862 | |||
| 09185e5f1c | |||
| 201f276c8b | |||
| 224ee281ef | |||
| f907d50333 | |||
| 95ce659ec3 | |||
| f9f6b34549 | |||
| bee7e51d06 | |||
| 51a2ff0976 | |||
| 1b298ff18b | |||
| 86d13d3a6e | |||
| 5421372754 | |||
| 53b29939f1 | |||
| 7bda8db86f | |||
| a7a48cd32b | |||
| b8e0836a6d | |||
| a7e6b2892c | |||
| f886ebfd75 | |||
| 98b334e6d7 | |||
| 83edb8007e | |||
| 67dda448f3 | |||
| 09953c0299 | |||
| 38b0fa65bc | |||
| 7200182c82 | |||
| e73e998de2 | |||
| b374f71533 | |||
| a69da2eb2a | |||
| a00c594d83 | |||
| 3df99552e1 | |||
| e2bc2824f6 | |||
| 36f4728f3a | |||
| 6ca72e7965 | |||
| 151dcc06da | |||
| 5737109dec | |||
| 4bd3a46f32 | |||
| 73e2d64fcd | |||
| 1f008b405d | |||
| 00e1e2602f | |||
| 98445da971 | |||
| ad363c64af | |||
| 82eed83dcc | |||
| 7c7a69c755 | |||
| af01beb583 | |||
| b7cd3db8bc | |||
| 056c35ac3f | |||
| f6a24038cc | |||
| fde5935adc | |||
| f8eecfc6d5 | |||
| 53aa19d1a1 | |||
| a49756ddaa | |||
| ee3fca35bd | |||
| 55ae4b1161 | |||
| 76473f7185 | |||
| ff87729ee8 | |||
| 9464b3a084 | |||
| d0bf90b900 | |||
| fec4b755bd | |||
| 73d973783d | |||
| 75e5de603e | |||
| 6da42a18c7 | |||
| f3c9fbdd2e | |||
| c7c7d57e7e | |||
| c3abdcf68b | |||
| 8f0727403d | |||
| 2301e54de4 | |||
| be952e65f4 | |||
| 24457ebe36 | |||
| a523d3dd2b | |||
| 373fd4c8e8 | |||
| 106148fd9e | |||
| 29261082f1 | |||
| dbc1490e10 | |||
| b6400c72a7 | |||
| 719c6d5fbb | |||
| f33a0aceb2 | |||
| 7141ae9544 | |||
| af638ab2f7 | |||
| a75a6c6114 | |||
| 438f5d7a2e | |||
| dec5b1ac2c | |||
| 726a306830 | |||
| 330c78a92c | |||
| dcec7c7281 | |||
| b9ba25c116 | |||
| 0341a603d5 | |||
| d91a3e056a | |||
| 9c1042f686 | |||
| cad3fee1b9 | |||
| 5947b2ee62 | |||
| 177a39bad1 | |||
| cdf8f6e32d | |||
| f88aac11e9 | |||
| 05b905b988 | |||
| 669cd2892d | |||
| dd07d08ff0 | |||
| 54bb327fdf | |||
| 2cd8b46c1f | |||
| b61413f7f1 | |||
| 4e7765a7e7 | |||
| 49a6dc43c1 | |||
| b82bc6698f | |||
| 1e43c67660 | |||
| c129571167 | |||
| b23a643c0e | |||
| 194bff3e6a | |||
| 426ca78288 | |||
| f15919fdce | |||
| 2124842136 | |||
| 70e697876b | |||
| 9b5e5a871c | |||
| 5ba92e5de5 | |||
| 4e6780a3cf | |||
| 00584ad635 | |||
| f72ed33515 | |||
| c2366ed1ba | |||
| 46df8d7311 | |||
| d4579b9184 | |||
| ee6f0cfe70 | |||
| 6155fd9c68 | |||
| 32c0608b51 | |||
| 404df6af23 | |||
| 764e10538b | |||
| 6b1b590e30 | |||
| 8360e520cd | |||
| 005a17d750 | |||
| 28abe036a0 | |||
| ca96112b67 | |||
| b8e847c6a6 | |||
| 35cb2c31fc | |||
| 30dd587d34 | |||
| 4644814582 | |||
| 7db3151d8a | |||
| c8ca73cca9 | |||
| d5a0479ebe | |||
| 466b3bbc27 | |||
| df14912693 | |||
| 8a53cc6150 | |||
| 339ec69a72 | |||
| b2cb717aa3 | |||
| cf4a02f37f | |||
| 84304dd7ce | |||
| e3b44eb194 | |||
| 2cbb0a590f | |||
| 6b9c864890 | |||
| f5730bf945 | |||
| a1ae3a2b8b | |||
| c15a0763d5 | |||
| 4a23d88f30 | |||
| 286705c79b | |||
| 3895f5c531 | |||
| c51e0bd218 | |||
| 766570aeb2 | |||
| 4e91b0d19d | |||
| 65f7630546 | |||
| 8cf7572041 | |||
| db057a5ca9 |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"homepage": "https://www.curseforge.com/minecraft/mc-mods/electroblobs-wizardry",
|
||||
"promos": {
|
||||
"1.12.2-latest": "4.3.5",
|
||||
"1.12.2-recommended": "4.3.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a crash or something that isn't working as it should
|
||||
title: "[Give a brief, relevant title here]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting. You may also find the [troubleshooting page](https://github.com/Electroblob77/Wizardry/wiki/Troubleshooting) helpful.
|
||||
|
||||
Minecraft version: 1.12.2 [change as necessary]
|
||||
Wizardry version: 4.3.5 [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.]
|
||||
|
||||
Other mods involved: [only list mods that are relevant to the problem]
|
||||
|
||||
Link to crash report (if applicable): [give a link to the crash report on gist, pastebin, etc.]
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Make a suggestion for improving the mod
|
||||
title: "[Give a brief, relevant title here]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting.
|
||||
|
||||
Minecraft versions: [if this suggestion only applies to some versions, list them here]
|
||||
|
||||
Suggestion details: [describe what you would like to see added to the mod and why]
|
||||
|
||||
Other mods involved: [if this suggestion involves integration with other mods, list them here]
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about the mod or the code
|
||||
title: "[Give a brief, relevant title here]"
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting.
|
||||
|
||||
Minecraft version: 1.12.2 [change as necessary]
|
||||
Wizardry version: [change as necessary]
|
||||
|
||||
Question details: [explain your question here]
|
||||
|
||||
Other mods involved: [if this question involves other mods, list them here]
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Potential Duplicates
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: bubkoo/potential-duplicates@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
label: potential duplicate
|
||||
exclude: |
|
||||
adding
|
||||
add
|
||||
creating
|
||||
create
|
||||
icons
|
||||
icon
|
||||
logos
|
||||
logo
|
||||
missing
|
||||
miss
|
||||
needing
|
||||
need
|
||||
please
|
||||
requesting
|
||||
request
|
||||
1.12.2
|
||||
wizardry
|
||||
state: all
|
||||
threshold: 0.7
|
||||
comment: |
|
||||
This issue is potentially a duplicate of one of the following issues:
|
||||
{{#issues}}
|
||||
- [#{{ number }}] {{ title }} ({{ accuracy }}% match)
|
||||
{{/issues}}
|
||||
+6
-1
@@ -20,5 +20,10 @@ build
|
||||
# other
|
||||
eclipse
|
||||
run
|
||||
misc
|
||||
logs
|
||||
/misc
|
||||
/libs
|
||||
libs_src
|
||||
libsfordeobf
|
||||
generated
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,59 @@
|
||||
# Guide for contributing to this repository
|
||||
|
||||
## Submitting an issue
|
||||
|
||||
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.
|
||||
|
||||
- One issue per issue please! Make a separate issue for each bug/suggestion. If you think multiple different issues may be related, reference between them using # followed by the issue number, and github will automagically link them.
|
||||
|
||||
- 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!
|
||||
|
||||
- Make sure your wizardry and Forge versions are both up-to-date for the game version you are using.
|
||||
|
||||
- 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.
|
||||
|
||||
- Please don't revive old issues, _especially_ if they have been fixed, because I don't routinely check them. Make a new issue and link the old one to it if you think it's related.
|
||||
|
||||
- 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
|
||||
|
||||
I like my code to be tidy, so it saves me time if yours is too. Here are a few general guidelines for how to submit a good PR:
|
||||
|
||||
- Use proper indentation and try to space your code out nicely. This really goes without saying - select all your code and hit ctrl-i before you submit it, and try to use my spacing if you can (for instance, don't do what the Minecraft source does and put a { on a separate line, it's a waste of space).
|
||||
|
||||
- Commented code is dead code, UNLESS it has an accompanying comment detailing an appropriate reason for it being commented. (something like "DO NOT REMOVE, commented because it will be useful in 1.12" or "DO NOT REMOVE, commented for debugging purposes, uncomment when fixed")
|
||||
|
||||
- Please comment your methods/fields/classes! Preferably using Javadoc, but for small/private methods one line comments are OK. I find it hard enough to remember what I wrote a method for if it isn't commented, let alone trying to work out what yours are for. That said, you should try to name the method/field/class so it's obvious what it is anyway.
|
||||
|
||||
- Please use @Override when overriding any methods.
|
||||
|
||||
- Follow naming conventions. BLOCK_CAPITALS for constants, CapitalisedWords for classes, camelCase for almost everything else.
|
||||
|
||||
- This isn't code related, but when you submit a PR, please tell me what it does! You don't need much, just something like "Makes item X do X" or "Fixes #162". If you're resolving an issue, put "Fixes", "Closes" or "Resolves" and then the issue number and Github will close it automatically.
|
||||
|
||||
## Guide for translators
|
||||
|
||||
Pull requests aren't just for code - if you would like to help translate Wizardry, you can do that here too! Simply fork the repository, add your translations and submit a PR when you're done. Wizardry has TWO files you'll need to translate: the usual .lang file and the handbook text file, which can be found in resources/assets/wizardry/texts.
|
||||
|
||||
- Firstly, the .lang file: if you're familiar with translating mods, this is all pretty standard - it holds the names of blocks, items, mobs and so on. Ignore the %X$s bits, they're placeholders for stuff that gets put in by Wizardry when the game is running.
|
||||
|
||||
- Secondly, the handbook JSON file: the US english version is named handbook_en_us.json, and (rather obviously) other languages' text files replace 'en_us' with the name of their corresponding .lang file. Make sure you only change the _values_ (after each `:`), not the identifiers - for a summary of JSON syntax, see the [JSON webpage](https://www.json.org/json-en.html). If you're familiar with advancements, loot tables and similar files, this won't be new to you. The handbook file also applies its own form of markup to the main text, with the following features:
|
||||
|
||||
- `#image tag` adds an image to the text with the given tag. Image tags are defined in the `"images": {...}` block at the start of the file. Each named entry in this block defines a single image with that name as its tag. Each entry is a JSON object containing a file location and optional caption/dimensions.
|
||||
- `#recipe tag` adds a crafting recipe to the text with the given tag. Similar to image tags, recipe tags are defined in the `"recipes": {...}` block at the start of the file. Each named entry in this block defines a recipe with that name as its tag. Each entry contains a list of one or more locations or recipes to display (if more than one is specified, the display will cycle through the different recipes).
|
||||
- Anything else with a `#` directly before it (with no space) will be treated as a format argument if possible. This means the text after the `#` will be replaced with something else at runtime, similar to the `%X$s` bits in a normal .lang file, if it matches one of the available format arguments. The current format arguments can be found [here](https://github.com/Electroblob77/Wizardry/blob/1.12.2/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java#L177-L203).
|
||||
- `@section@` adds a hyperlink to the given section, which will display as the name of that section. `@section alt text@` is the same, except alt text is displayed instead of the section name (alt text may contain spaces). It is also possible to hyperlink to a webpage, simply replace the section name with the url (e.g. `@https://example.com/@` or `@https://example.com/ alt text@`).
|
||||
|
||||
> Translators for Chinese, Japanese, Korean and similar East Asian languages: Due to how Minecraft's word wrapping works, for best results you should _remove spaces before and after hyperlinks and formatting tags_, excluding images and recipes. However, do not remove the space between a hyperlink's destination and its alt text or the link will break!
|
||||
|
||||
If all of that confused you, the simple rule is: _Don't translate anything directly after an `@` symbol, directly after a `#`, or the first word after `#image` or `#recipe`_. However, you should translate the `"caption"` entries in the `images` block.
|
||||
|
||||
**Please ensure all translation files are encoded in UTF-8 format, otherwise they might crash the game!**
|
||||
+39
-5
@@ -1,8 +1,8 @@
|
||||
# Credits
|
||||
|
||||
Electroblob's Wizardry
|
||||
Version 3.1.0
|
||||
For Minecraft 1.11.2
|
||||
Version 4.3.5
|
||||
For Minecraft 1.12.2
|
||||
|
||||
Designed, coded and textured by Electroblob
|
||||
|
||||
@@ -12,13 +12,47 @@ Thanks also to the Minecraft modding community, which always has an answer to my
|
||||
|
||||
In addition, I'd like to thank the following individuals for their contributions to the mod:
|
||||
|
||||
#### Discord Moderators (and all-round awesome people)
|
||||
|
||||
- FavouriteDragon
|
||||
- WinDanesz
|
||||
|
||||
#### Code
|
||||
|
||||
- Corail31
|
||||
- 12foo
|
||||
- Shadows-of-Fire
|
||||
- HellFirePvP
|
||||
- Tora-B
|
||||
- Avatair
|
||||
- Aeronica
|
||||
- UltraHex
|
||||
- Azim-Palmer
|
||||
- raoulvdberge
|
||||
- rafasoares
|
||||
- xinyuan-liu
|
||||
- SettingDust
|
||||
- Aralu115
|
||||
|
||||
#### Translations
|
||||
|
||||
- Russian: VilagVil
|
||||
- Spanish and Mexican Spanish: MadWrist
|
||||
- Spanish: MadWrist, Alsentar
|
||||
- Mexican Spanish: MadWrist
|
||||
- Russian: VilagVil, kellixon, bigenergy, MugGod2, DrHesperus
|
||||
- French: Hahdrim, Crowller
|
||||
- Brazilian Portuguese: lorrampi
|
||||
- Chinese (Simplified): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer
|
||||
- Korean: shejery, rewi_wire, 방통, red1854th
|
||||
- Polish: Trozuu, Olej
|
||||
- German: BirdyDragon, Lemopav
|
||||
- Chinese (Traditional): chesterccj305
|
||||
- Hungarian: Bombadil
|
||||
|
||||
Lightning ray sound effect from OhhWowProductions
|
||||
#### Sound Effects
|
||||
- OhhWowProductions
|
||||
- fredzed
|
||||
- deleted_user_3277771
|
||||
- DiscoveryME
|
||||
- OGSoundFX
|
||||
- leosalom
|
||||
- juskiddink
|
||||
+4
-2
@@ -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. You may also find the [troubleshooting page](https://github.com/Electroblob77/Wizardry/wiki/Troubleshooting) helpful.
|
||||
|
||||
Minecraft version: 1.12.2 [change as necessary]
|
||||
Wizardry version: [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.]
|
||||
|
||||
+4
-4
@@ -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. By downloading, running, forking, contributing to or otherwise interacting with this repository, you agree to the license set out below.
|
||||
|
||||
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
|
||||
|
||||
@@ -27,7 +27,7 @@ TL;DR: Do what you want with this repo on Github, but ask me first if you want t
|
||||
|
||||
### 1. Warranty and usage
|
||||
|
||||
This repository, the source code for the Minecraft modification Electroblob's Wizardry ('Wizardry'), is provided in the hope that it will be useful and/or educational. You are free to download and modify this repository for personal use only. See section 2 for details regarding public sharing of this repository.
|
||||
This repository, the source code for the Minecraft modification Electroblob's Wizardry ('Wizardry'), is provided in the hope that it will be useful and/or educational. You are free to download and modify this repository for personal use. See section 2 for details regarding public sharing of this repository.
|
||||
|
||||
THIS REPOSITORY IS PROVIDED AS-IS WITH NO WARRANTIES, IMPLIED OR OTHERWISE. ELECTROBLOB TAKES NO RESPONSIBILITY FOR ANY DAMAGES INCURRED BY THE USE OR MISUSE OF THIS REPOSITORY.
|
||||
|
||||
@@ -37,7 +37,7 @@ In addition to private use as set out in section 1, you are free to fork this re
|
||||
|
||||
### 3. Distribution
|
||||
|
||||
Distribution, in whole or in part, of compiled versions of wizardry, and distribution of the source code anywhere except on Github, modified or otherwise, is forbidden without advance written permission from Electroblob. Electroblob reserves the right to refuse or withdraw such permission at any time.
|
||||
Distribution of this repository anywhere except on Github, in whole or in part, modified or otherwise, is forbidden without advance written permission from Electroblob. Electroblob reserves the right to refuse or withdraw such permission at any time.
|
||||
|
||||
### 4. Commercial use
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Wizardry [](https://minecraft.curseforge.com/projects/electroblobs-wizardry) [](https://minecraft.curseforge.com/projects/electroblobs-wizardry) [](https://discord.gg/MTmMzMv)
|
||||
# Wizardry [](https://minecraft.curseforge.com/projects/electroblobs-wizardry) [](https://minecraft.curseforge.com/projects/electroblobs-wizardry/files) [](https://discord.gg/hs8yJP2)
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
Please read the [guide for contributing](CONTRIBUTING.md) before submitting an issue or a PR.
|
||||
|
||||
<b>Links</b>
|
||||
|
||||
|
||||
+31
-21
@@ -11,17 +11,34 @@ 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.3.5" // There, it matches semver, happy now?
|
||||
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"
|
||||
}
|
||||
maven {
|
||||
name = "Curseforge Maven"
|
||||
url = "https://minecraft.curseforge.com/api/maven/"
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
version = "1.12.2-14.23.2.2611"
|
||||
version = "1.12.2-14.23.5.2847"
|
||||
runDir = "run"
|
||||
|
||||
// the mappings can be changed at any time, and must be in the following format.
|
||||
@@ -29,32 +46,25 @@ 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.
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// you may put jars on which you depend on in ./libs
|
||||
// or you may define them like so..
|
||||
//compile "some.group:artifact:version:classifier"
|
||||
//compile "some.group:artifact:version"
|
||||
|
||||
// real examples
|
||||
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
|
||||
//compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
|
||||
|
||||
// the 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
|
||||
//provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
|
||||
// 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}"
|
||||
|
||||
// the deobf configurations: 'deobfCompile' and 'deobfProvided' are the same as the normal compile and provided,
|
||||
// except that these dependencies get remapped to your current MCP mappings
|
||||
//deobfCompile 'com.mod-buildcraft:buildcraft:6.0.8:dev'
|
||||
//deobfProvided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
|
||||
deobfCompile "baubles:Baubles:${mc_version_short}:${baubles_version}"
|
||||
deobfCompile "antique-atlas:antiqueatlas:${mc_version}:${antique_atlas_version}"
|
||||
|
||||
// for more info...
|
||||
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
|
||||
// http://www.gradle.org/docs/current/userguide/dependency_management.html
|
||||
}
|
||||
|
||||
task deobfJar(type: Jar) {
|
||||
from sourceSets.main.output
|
||||
classifier = 'deobf'
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# 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
|
||||
mc_version_short=1.12
|
||||
jei_version=4.15.0.291
|
||||
baubles_version=1.5.2
|
||||
antique_atlas_version=4.6.3
|
||||
Vendored
BIN
Binary file not shown.
+1
-2
@@ -1,6 +1,5 @@
|
||||
#Mon Sep 14 12:28:28 PDT 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+4
-10
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Guide for contributing to this repository
|
||||
|
||||
## 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.
|
||||
|
||||
## Submitting a PR
|
||||
|
||||
I like my code to be tidy, so it saves me time if yours is too. Here are a few general guidelines for how to submit a good PR:
|
||||
|
||||
- Use proper indentation and try to space your code out nicely. This really goes without saying - select all your code and hit ctrl-i before you submit it, and try to use my spacing if you can (for instance, don't do what the Minecraft source does and put a { on a separate line, it's a waste of space).
|
||||
|
||||
- Commented code is dead code, UNLESS it has an accompanying comment detailing an appropriate reason for it being commented. (something like "DO NOT REMOVE, commented because it will be useful in 1.12" or "DO NOT REMOVE, commented for debugging purposes, uncomment when fixed")
|
||||
|
||||
- Please comment your methods/fields/classes! Preferably using Javadoc, but for small/private methods one line comments are OK. I find it hard enough to remember what I wrote a method for if it isn't commented, let alone trying to work out what yours are for. That said, you should try to name the method/field/class so it's obvious what it is anyway.
|
||||
|
||||
- Please use @Override when overriding any methods.
|
||||
|
||||
- Follow naming conventions. BLOCK_CAPITALS for constants, CapitalisedWords for classes, camelCase for almost everything else.
|
||||
|
||||
- This isn't code related, but when you submit a PR, please tell me what it does! You don't need much, just something like "Makes item X do X" or "Fixes #162". If you're resolving an issue, put "Fixes", "Closes" or "Resolves" and then the issue number and Github will close it automatically.
|
||||
|
||||
## Guide for translators
|
||||
|
||||
Pull requests aren't just for code - if you would like to help translate Wizardry, you can do that here too! Simply fork the repository, add your translations and submit a PR when you're done. Wizardry has TWO files you'll need to translate: the usual .lang file and the handbook text file, which can be found in resources/assets/wizardry/texts.
|
||||
|
||||
- Firstly, the .lang file: if you're familiar with translating mods, this is all pretty standard - it holds the names of blocks, items, mobs and so on. Ignore the %X$s bits, they're placeholders for stuff that gets put in by Wizardry when the game is running.
|
||||
|
||||
- Secondly, the handbook text file: the US english version is named handbook_en_US.txt, and (rather obviously) other languages' text files replace 'en_US' with the name of their corresponding .lang file. Anything wirtten in BLOCK CAPITALS should be left alone, because Wizardry replaces it with the appropriate text when the game is running.
|
||||
@@ -1,32 +1,37 @@
|
||||
package electroblob.wizardry;
|
||||
|
||||
import electroblob.wizardry.block.BlockBookshelf;
|
||||
import electroblob.wizardry.inventory.ContainerArcaneWorkbench;
|
||||
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.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemArmor.ArmorMaterial;
|
||||
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 javax.annotation.Nullable;
|
||||
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 +41,36 @@ public class CommonProxy {
|
||||
// SECTION Registry
|
||||
// ===============================================================================================================
|
||||
|
||||
public void registerRenderers(){
|
||||
}
|
||||
public void registerRenderers(){}
|
||||
|
||||
public void initialiseLayers(){
|
||||
}
|
||||
public void initialiseLayers(){}
|
||||
|
||||
public void registerKeyBindings(){
|
||||
}
|
||||
public void initialiseAnimations(){}
|
||||
|
||||
public void registerSpellHUD(){}
|
||||
public void registerKeyBindings(){}
|
||||
|
||||
public net.minecraft.client.model.ModelBiped getWizardArmourModel(){
|
||||
public net.minecraft.client.model.ModelBiped getWizardArmourModel(ArmorMaterial material){
|
||||
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 +80,23 @@ public class CommonProxy {
|
||||
// SECTION Items
|
||||
// ===============================================================================================================
|
||||
|
||||
public boolean shouldDisplayDiscovered(Spell spell, @Nullable 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 +106,84 @@ public class CommonProxy {
|
||||
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given key with no specified style. Client-side only; on the server this simply returns
|
||||
* the given translation key. Useful whenever translation from common classes is required, e.g. item tooltips.
|
||||
* @param key The unlocalised name to be translated.
|
||||
* @param args The format arguments to pass into the translation, if any.
|
||||
* @return The resulting translated text.
|
||||
*/
|
||||
public String translate(String key, Object... args){
|
||||
return translate(key, new Style(), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given key and formats it with the given style. Client-side only; on the server this simply returns
|
||||
* the given translation key. Useful whenever translation from common classes is required, e.g. item tooltips.
|
||||
* @param key The unlocalised name to be translated.
|
||||
* @param style The {@link Style} to use for the displayed text.
|
||||
* @param args The format arguments to pass into the translation, if any.
|
||||
* @return The resulting translated text.
|
||||
*/
|
||||
public String translate(String key, Style style, Object... args){
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style, Object...)}, but style defaults to light grey. */
|
||||
public void addMultiLineDescription(List<String> tooltip, String key, Object... args){
|
||||
this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Object... args){}
|
||||
|
||||
// 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 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,19 +191,144 @@ 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 the spell charge-up sound at the given entity.
|
||||
* @param entity The source of the sound
|
||||
*/
|
||||
public void playChargeupSound(EntityLivingBase entity){}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client side world using Minecraft.getMinecraft().world. <b>Only to be called client side!</b> Returns
|
||||
* null on the server side.
|
||||
* 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);
|
||||
}
|
||||
|
||||
/** Starts the first-person blink overlay effect for the specified player. */
|
||||
public void playBlinkEffect(EntityPlayer player){}
|
||||
|
||||
/**
|
||||
* Starts the client-side screen shake effect for the specified player.
|
||||
* @param player The player whose screen is to be shaken
|
||||
* @param intensity The amplitude of the shaking (around 10 looks about right)
|
||||
*/
|
||||
public void shakeScreen(EntityPlayer player, float intensity){}
|
||||
|
||||
/**
|
||||
* Loads the given shader for the given player, if they have shaders enabled.
|
||||
* @param player The player to load the shader for
|
||||
* @param shader The location of the shader to load
|
||||
*/
|
||||
public void loadShader(EntityPlayer player, ResourceLocation shader){}
|
||||
|
||||
/**
|
||||
* Gets the client-side world using {@code Minecraft.getMinecraft().world}. <b>Only to be called client side!</b>
|
||||
* Returns null on the server side.
|
||||
*/
|
||||
public World getTheWorld(){
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client-side player using Minecraft.getMinecraft().player. <b>Only to be called client side!</b> Returns
|
||||
* null on the server side.
|
||||
*/
|
||||
public EntityPlayer getThePlayer(){
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the game is being viewed from the perspective of the given entity and is set to first-person
|
||||
* view. Always returns false on the server side.
|
||||
*/
|
||||
public boolean isFirstPerson(Entity entity){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */
|
||||
public Set<String> getSpellHUDSkins(){
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Notifies nearby players of a bookshelf change, causing any lectern or arcane workbench GUI (client-side) or
|
||||
* container (both sides) to refresh its linked bookshelves (does not send packets). */
|
||||
public void notifyBookshelfChange(World world, BlockPos pos){
|
||||
for(EntityPlayer player : world.playerEntities){
|
||||
if(player.getDistanceSq(pos) < BlockBookshelf.PLAYER_NOTIFY_RANGE * BlockBookshelf.PLAYER_NOTIFY_RANGE){
|
||||
if(player.openContainer instanceof ContainerArcaneWorkbench){
|
||||
((ContainerArcaneWorkbench)player.openContainer).refreshBookshelfSlots();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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. */
|
||||
|
||||
}
|
||||
@@ -1,23 +1,37 @@
|
||||
package electroblob.wizardry;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import electroblob.wizardry.block.BlockBookshelf;
|
||||
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.inventory.ContainerBookshelf;
|
||||
import electroblob.wizardry.misc.DonationPerksHandler;
|
||||
import electroblob.wizardry.misc.Forfeit;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryRegistry;
|
||||
import electroblob.wizardry.registry.WizardryTabs;
|
||||
import electroblob.wizardry.registry.WizardryLoot;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellProperties;
|
||||
import electroblob.wizardry.util.WandHelper;
|
||||
import electroblob.wizardry.worldgen.WorldGenCrystalFlower;
|
||||
import electroblob.wizardry.worldgen.WorldGenCrystalOre;
|
||||
import electroblob.wizardry.worldgen.WorldGenLibraryRuins;
|
||||
import electroblob.wizardry.worldgen.WorldGenObelisk;
|
||||
import electroblob.wizardry.worldgen.WorldGenShrine;
|
||||
import electroblob.wizardry.worldgen.WorldGenUndergroundLibraryRuins;
|
||||
import electroblob.wizardry.worldgen.WorldGenWizardTower;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
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,72 +44,75 @@ 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* <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, acceptedMinecraftVersions = "[1.12.2]",
|
||||
guiFactory = "electroblob.wizardry.WizardryGuiFactory",
|
||||
updateJSON = Wizardry.updateJSON,
|
||||
dependencies = "required-after:forge@[14.23.5.2847,);after:jei@[4.15.0,);after:baubles@[1.5.2,);after:antiqueatlas@[4.6,)")
|
||||
|
||||
@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>
|
||||
* <center><b>[major Minecraft version].[major mod version].[minor mod version/patch]
|
||||
* <p>
|
||||
* </center></b> The major mod version is consistent across Minecraft versions, i.e. Wizardry 1.1 has the same
|
||||
* <p></p>
|
||||
* <center><b>[major Minecraft version].[major mod version].[minor mod version/patch]</b>
|
||||
* <p></p>
|
||||
* </center> 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.3.5";
|
||||
|
||||
/**
|
||||
* Json file used by Forge's built-in <a href="https://mcforge.readthedocs.io/en/1.12.x/gettingstarted/autoupdate/">update checker</a>.
|
||||
* Used by the in-game mod menu to show that green icon whenever a newer version is available of the mod.
|
||||
*/
|
||||
public static final String updateJSON = "https://github.com/Electroblob77/Wizardry/blob/1.12.2/.forge/update.json";
|
||||
|
||||
// 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
|
||||
|
||||
/** 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;
|
||||
/** A {@link File} object representing wizardry's config folder, {@code config/ebwizardry}). As of wizardry 4.2.4,
|
||||
* this folder contains the main config file and the global spell properties folder, if used. */
|
||||
public static File configDirectory;
|
||||
|
||||
// EventManager
|
||||
WizardryWorldGenerator generator = new WizardryWorldGenerator();
|
||||
public static boolean tisTheSeason;
|
||||
|
||||
// The instance of wizardry that Forge uses.
|
||||
@Instance(Wizardry.MODID)
|
||||
@@ -109,39 +126,37 @@ public class Wizardry {
|
||||
public void preInit(FMLPreInitializationEvent event){
|
||||
|
||||
logger = event.getModLog();
|
||||
|
||||
proxy.registerResourceReloadListeners();
|
||||
|
||||
configDirectory = new File(event.getModConfigurationDirectory(), Wizardry.MODID);
|
||||
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;
|
||||
}
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
tisTheSeason = calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DAY_OF_MONTH) >= 24
|
||||
&& calendar.get(Calendar.DAY_OF_MONTH) <= 26;
|
||||
|
||||
@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();
|
||||
BlockBookshelf.registerStandardBookModelTextures();
|
||||
|
||||
// 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();
|
||||
|
||||
// Commented out for 4.2.3 to get rid of the sound bug, reinstate once a fix is found.
|
||||
WizardrySounds.SPELLS = SoundCategory.PLAYERS;//CustomSoundCategory.add(Wizardry.MODID + "_spells");
|
||||
|
||||
WizardryBaublesIntegration.init();
|
||||
WizardryAntiqueAtlasIntegration.init();
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -149,27 +164,44 @@ 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);
|
||||
GameRegistry.registerWorldGenerator(new WorldGenLibraryRuins(), 20);
|
||||
GameRegistry.registerWorldGenerator(new WorldGenUndergroundLibraryRuins(), 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
|
||||
BlockBookshelf.compileBookModelTextures();
|
||||
ContainerBookshelf.initDefaultBookItems();
|
||||
WizardryItems.registerDispenseBehaviours();
|
||||
WizardryItems.registerBannerPatterns();
|
||||
WandHelper.populateUpgradeMap();
|
||||
Spell.registry.forEach(Spell::init);
|
||||
SpellProperties.init();
|
||||
|
||||
// Client-side stuff (via proxies)
|
||||
proxy.initGuiBits();
|
||||
proxy.registerParticles();
|
||||
proxy.registerSoundEventListener();
|
||||
}
|
||||
|
||||
@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();
|
||||
proxy.initialiseAnimations();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -184,30 +216,42 @@ public class Wizardry {
|
||||
public void onConfigChanged(net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent event){
|
||||
if(event.getModID().equals(Wizardry.MODID)){
|
||||
settings.saveConfigChanges();
|
||||
// All of the synchronised settings require a world restart anyway so this doesn't need syncing.
|
||||
// All of the synchronised settings require a world restart anyway so don't need syncing, except for the
|
||||
// donor perks which have special behaviour (since we have to update everyone when a donor logs in anyway,
|
||||
// we might as well reuse the packet and let them change the setting mid-game)
|
||||
if(event.isWorldRunning() && DonationPerksHandler.isDonor(proxy.getThePlayer())){
|
||||
DonationPerksHandler.sendToServer(proxy.getThePlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 +294,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,143 @@
|
||||
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.init.MobEffects;
|
||||
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.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.storage.loot.LootContext;
|
||||
import net.minecraft.world.storage.loot.LootTable;
|
||||
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;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
Wizardry.settings.sync((EntityPlayerMP)event.player);
|
||||
EntityPlayerMP player = (EntityPlayerMP)event.player;
|
||||
SpellGlyphData.get(player.world).sync(player);
|
||||
SpellEmitterData.get(player.world).sync(player);
|
||||
Wizardry.settings.sync(player);
|
||||
syncAdvancements(player, false);
|
||||
Spell.syncProperties(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 +146,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);
|
||||
EntityUtils.playSoundAtPlayer(player, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
|
||||
player.sendMessage(new TextComponentTranslation("spell.discover",
|
||||
event.getSpell().getNameForTranslationFormatted()));
|
||||
}
|
||||
@@ -155,33 +184,76 @@ 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.getEntityLiving() instanceof EntityLiving && event.getTarget() != null){
|
||||
|
||||
// Muffle
|
||||
if(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);
|
||||
}
|
||||
}
|
||||
|
||||
// Mirage
|
||||
if(event.getTarget().isPotionActive(WizardryPotions.mirage)){
|
||||
// Can't find players under mirage at all! (Pretty sure this excludes revenge-targeting)
|
||||
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
|
||||
}
|
||||
|
||||
// Blindness tweak
|
||||
// I'm not going as far as potion core's implementation, this is just so it does *something* to mobs
|
||||
if(event.getEntityLiving().isPotionActive(MobEffects.BLINDNESS) && !Loader.isModLoaded("potioncore")
|
||||
&& Wizardry.settings.blindnessTweak && event.getTarget().getDistanceSq(event.getEntity()) > 3.5 * 3.5){
|
||||
// Can't detect anything more than 3.5 blocks away (roughly the player's view distance when blinded)
|
||||
((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,56 +264,59 @@ 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.posY + 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){
|
||||
|
||||
// Ward
|
||||
PotionEffect effect = event.getEntityLiving().getActivePotionEffect(WizardryPotions.ward);
|
||||
|
||||
if(effect != null && event.getSource().isMagicDamage()){
|
||||
event.setAmount(event.getAmount() * Math.max(0, 1 - 0.4f * effect.getAmplifier())); // Resistance is 20%
|
||||
}
|
||||
|
||||
// Flaming and freezing swords
|
||||
if(event.getSource().getTrueSource() instanceof EntityLivingBase){
|
||||
|
||||
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())){
|
||||
|
||||
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon,
|
||||
attacker.getHeldItemMainhand());
|
||||
@@ -282,31 +357,6 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(event.getEntityLiving().world.isRemote){
|
||||
|
||||
// Client-side continuous spell casting for NPCs
|
||||
@@ -315,132 +365,128 @@ public final class WizardryEventHandler {
|
||||
|
||||
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
|
||||
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
|
||||
int count = ((ISpellCaster)event.getEntity()).getSpellCounter();
|
||||
|
||||
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, count))){
|
||||
|
||||
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
|
||||
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, count,
|
||||
// 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);
|
||||
|
||||
((ISpellCaster)event.getEntity()).setSpellCounter(count + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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){
|
||||
|
||||
EntityPlayer player = (EntityPlayer)event.getSource().getTrueSource();
|
||||
|
||||
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
|
||||
// Compatibility with "Lycanites Mobs" -it uses custom loot drop logic which can't be hooked, given the
|
||||
// number of mobs that spawn as Lycanites when this mod is active it would massively nerf the wizard drops
|
||||
// if we didn't handle this
|
||||
if(event.getEntity().getClass().getCanonicalName().contains("lycanitesmobs"))
|
||||
{
|
||||
WorldServer world = (WorldServer)event.getEntity().getEntityWorld();
|
||||
LootTable table = world.getLootTableManager().getLootTableFromLocation(new ResourceLocation(Wizardry.MODID, "entities/mob_additions"));
|
||||
|
||||
if(stack.getItem() instanceof ItemWand && stack.isItemDamaged()
|
||||
&& WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
|
||||
int damage = stack.getItemDamage()
|
||||
- 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;
|
||||
LootContext ctx = new LootContext.Builder(world).withPlayer(player).build();
|
||||
List<ItemStack> stacks = table.generateLootForPools(world.rand, ctx);
|
||||
|
||||
for(ItemStack stack : stacks) {
|
||||
event.getEntity().entityDropItem(stack, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){
|
||||
WizardryAdvancementTriggers.self_destruct.triggerFor(player);
|
||||
for(ItemStack stack : InventoryUtils.getPrioritisedHotbarAndOffhand(player)){
|
||||
|
||||
if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
|
||||
&& WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
|
||||
|
||||
int mana = Constants.SIPHON_MANA_PER_LEVEL
|
||||
* WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade)
|
||||
+ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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){
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
private static final double LIVING_ENTITY_TERMINAL_VELOCITY = 3.92; // From Minecraft Wiki!
|
||||
|
||||
event.getDrops()
|
||||
.add(new EntityItem(event.getEntityLiving().world, event.getEntityLiving().posX,
|
||||
event.getEntityLiving().posY, event.getEntityLiving().posZ,
|
||||
new ItemStack(WizardryItems.spell_book, 1, id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final double LOG_LIVING_ENTITY_DRAG = Math.log(LIVING_ENTITY_DRAG);
|
||||
private static final double FALL_TICKS_ERROR_CORRECTION = 0.500841776608447;
|
||||
|
||||
// Private helper methods
|
||||
// ================================================================================================================
|
||||
@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(!event.getEntity().world.isRemote && 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...
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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(itemstack.getItem() instanceof ItemWand){
|
||||
wand = itemstack;
|
||||
}
|
||||
// At terminal velocity, there's no way of finding fall distance from velocity, and the entity is probably dead anyway!
|
||||
if(v > -3.9){
|
||||
|
||||
if(itemstack.getItem() instanceof ItemWizardArmour){
|
||||
armour = itemstack;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
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);
|
||||
// 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
|
||||
|
||||
// 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;
|
||||
|
||||
// DEBUG
|
||||
// if(event.getEntity() instanceof EntityPlayer){
|
||||
// Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y);
|
||||
// }
|
||||
|
||||
// Don't let it increase fall damage beyond vanilla values
|
||||
if(y < event.getDistance()) 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
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package electroblob.wizardry;
|
||||
|
||||
import electroblob.wizardry.inventory.ContainerArcaneWorkbench;
|
||||
import electroblob.wizardry.inventory.ContainerBookshelf;
|
||||
import electroblob.wizardry.inventory.ContainerPortableWorkbench;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.item.ItemWizardHandbook;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
|
||||
import electroblob.wizardry.tileentity.ContainerPortableWorkbench;
|
||||
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
|
||||
import electroblob.wizardry.tileentity.TileEntityBookshelf;
|
||||
import electroblob.wizardry.tileentity.TileEntityLectern;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
@@ -21,40 +23,82 @@ public class WizardryGuiHandler implements IGuiHandler {
|
||||
public static final int ARCANE_WORKBENCH = nextGuiId++;
|
||||
public static final int WIZARD_HANDBOOK = nextGuiId++;
|
||||
public static final int PORTABLE_CRAFTING = nextGuiId++;
|
||||
public static final int BOOKSHELF = nextGuiId++;
|
||||
public static final int LECTERN = nextGuiId++;
|
||||
|
||||
@Override
|
||||
public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z){
|
||||
|
||||
if(id == ARCANE_WORKBENCH){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
|
||||
|
||||
if(tileEntity instanceof TileEntityArcaneWorkbench){
|
||||
return new ContainerArcaneWorkbench(player.inventory, (TileEntityArcaneWorkbench)tileEntity);
|
||||
}
|
||||
|
||||
}else if(id == PORTABLE_CRAFTING){
|
||||
return new ContainerPortableWorkbench(player.inventory, world, new BlockPos(x, y, z));
|
||||
|
||||
}else if(id == BOOKSHELF){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
|
||||
|
||||
if(tileEntity instanceof TileEntityBookshelf){
|
||||
return new ContainerBookshelf(player.inventory, (TileEntityBookshelf)tileEntity);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z){
|
||||
|
||||
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(player.getHeldItemMainhand());
|
||||
}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(player.getHeldItemOffhand());
|
||||
}
|
||||
|
||||
}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));
|
||||
|
||||
}else if(id == BOOKSHELF){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
|
||||
|
||||
if(tileEntity instanceof TileEntityBookshelf){
|
||||
return new electroblob.wizardry.client.gui.GuiBookshelf(player.inventory,
|
||||
(TileEntityBookshelf)tileEntity);
|
||||
}
|
||||
|
||||
}else if(id == LECTERN){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
|
||||
|
||||
if(tileEntity instanceof TileEntityLectern){
|
||||
return new electroblob.wizardry.client.gui.GuiLectern((TileEntityLectern)tileEntity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -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.WorldGenWizardryStructure;
|
||||
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 WorldGenWizardryStructure structureType;
|
||||
|
||||
public Instance(ResourceLocation criterionIn, String name){
|
||||
super(criterionIn);
|
||||
this.structureType = WorldGenWizardryStructure.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,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 or imbuement altar. The majority of any
|
||||
* ICriterionTrigger class is just boilerplate, and this is no exception. */
|
||||
public class WizardryContainerTrigger implements ICriterionTrigger<WizardryContainerTrigger.Instance> {
|
||||
|
||||
private final ResourceLocation id;
|
||||
private final Map<PlayerAdvancements, WizardryContainerTrigger.Listeners> listeners = Maps.newHashMap();
|
||||
|
||||
public WizardryContainerTrigger(ResourceLocation id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ResourceLocation getId(){
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void addListener(PlayerAdvancements advancements, Listener<WizardryContainerTrigger.Instance> listener){
|
||||
|
||||
WizardryContainerTrigger.Listeners listeners = this.listeners.get(advancements);
|
||||
|
||||
if(listeners == null){
|
||||
listeners = new WizardryContainerTrigger.Listeners(advancements);
|
||||
this.listeners.put(advancements, listeners);
|
||||
}
|
||||
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(PlayerAdvancements advancements, Listener<WizardryContainerTrigger.Instance> listener){
|
||||
|
||||
WizardryContainerTrigger.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 WizardryContainerTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
|
||||
return new WizardryContainerTrigger.Instance(this.id, ItemPredicate.deserialize(json.get("item")));
|
||||
}
|
||||
|
||||
public void trigger(EntityPlayerMP player, ItemStack stack){
|
||||
|
||||
WizardryContainerTrigger.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<WizardryContainerTrigger.Instance>> listeners = Sets.newHashSet();
|
||||
|
||||
public Listeners(PlayerAdvancements advancements){
|
||||
this.playerAdvancements = advancements;
|
||||
}
|
||||
|
||||
public boolean isEmpty(){
|
||||
return this.listeners.isEmpty();
|
||||
}
|
||||
|
||||
public void add(Listener<WizardryContainerTrigger.Instance> listener){
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void remove(Listener<WizardryContainerTrigger.Instance> listener){
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void trigger(ItemStack stack){
|
||||
|
||||
List<Listener<WizardryContainerTrigger.Instance>> list = null;
|
||||
|
||||
for(Listener<WizardryContainerTrigger.Instance> listener : this.listeners){
|
||||
|
||||
if(listener.getCriterionInstance().test(stack)){
|
||||
|
||||
if(list == null){
|
||||
list = Lists.newArrayList();
|
||||
}
|
||||
|
||||
list.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
if(list != null){
|
||||
for(Listener<WizardryContainerTrigger.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,367 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import electroblob.wizardry.Settings;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.WizardryGuiHandler;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryTabs;
|
||||
import electroblob.wizardry.tileentity.TileEntityBookshelf;
|
||||
import net.minecraft.block.BlockHorizontal;
|
||||
import net.minecraft.block.ITileEntityProvider;
|
||||
import net.minecraft.block.SoundType;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.properties.PropertyInteger;
|
||||
import net.minecraft.block.state.BlockFaceShape;
|
||||
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.inventory.IInventory;
|
||||
import net.minecraft.inventory.InventoryHelper;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.*;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.IWorldEventListener;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.property.IExtendedBlockState;
|
||||
import net.minecraftforge.common.property.Properties;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvider {
|
||||
|
||||
/** When a bookshelf block (of any kind specified in the config) is added or removed, players within this range will
|
||||
* be notified of the change. */
|
||||
public static final double PLAYER_NOTIFY_RANGE = 32;
|
||||
public static final int SLOT_COUNT = 12;
|
||||
|
||||
public static final UnlistedPropertyInt[] BOOKS = new UnlistedPropertyInt[SLOT_COUNT];
|
||||
|
||||
private static final Map<Supplier<Item>, ResourceLocation> BOOK_TEXTURE_MAP = new HashMap<>();
|
||||
private static ImmutableList<Item> bookItems;
|
||||
private static ImmutableList<ResourceLocation> bookTextures;
|
||||
|
||||
public BlockBookshelf(){
|
||||
super(Material.WOOD);
|
||||
this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH));
|
||||
this.setCreativeTab(WizardryTabs.WIZARDRY);
|
||||
this.setHardness(2.0F);
|
||||
this.setResistance(5.0F);
|
||||
this.setSoundType(SoundType.WOOD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockStateContainer createBlockState(){
|
||||
// IProperty<?>[] properties = { FACING };
|
||||
// return new BlockStateContainer(this, ArrayUtils.addAll(properties, BOOKS));
|
||||
return new BlockStateContainer.Builder(this).add(FACING).add(BOOKS).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){
|
||||
return state.getValue(FACING).getAxis() == face.getAxis() ? BlockFaceShape.UNDEFINED : BlockFaceShape.SOLID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer){
|
||||
return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite());
|
||||
}
|
||||
|
||||
// Why on earth are these two not in BlockHorizontal?
|
||||
|
||||
@Override
|
||||
public IBlockState withRotation(IBlockState state, Rotation rotation){
|
||||
return state.withProperty(FACING, rotation.rotate(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState withMirror(IBlockState state, Mirror mirror){
|
||||
return state.withRotation(mirror.toRotation(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state){
|
||||
super.onBlockAdded(worldIn, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void breakBlock(World world, BlockPos pos, IBlockState block){
|
||||
|
||||
TileEntity tileentity = world.getTileEntity(pos);
|
||||
|
||||
if(tileentity instanceof TileEntityBookshelf){
|
||||
InventoryHelper.dropInventoryItems(world, pos, (TileEntityBookshelf)tileentity);
|
||||
}
|
||||
|
||||
super.breakBlock(world, pos, block); // For blocks that don't extend BlockContainer, this removes the TE
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getStateFromMeta(int meta){
|
||||
EnumFacing enumfacing = EnumFacing.byIndex(meta);
|
||||
if(enumfacing.getAxis() == EnumFacing.Axis.Y) enumfacing = EnumFacing.NORTH;
|
||||
return this.getDefaultState().withProperty(FACING, enumfacing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetaFromState(IBlockState state){
|
||||
return state.getValue(FACING).getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
|
||||
IExtendedBlockState s = (IExtendedBlockState)super.getExtendedState(state, world, pos);
|
||||
|
||||
if(world.getTileEntity(pos) instanceof TileEntityBookshelf){
|
||||
|
||||
TileEntityBookshelf tileentity = ((TileEntityBookshelf)world.getTileEntity(pos));
|
||||
|
||||
for(int i = 0; i < tileentity.getSizeInventory(); i++){
|
||||
|
||||
if(tileentity.getStackInSlot(i).isEmpty()){
|
||||
s = s.withProperty(BOOKS[i], bookItems.size());
|
||||
|
||||
}else{
|
||||
Item item = tileentity.getStackInSlot(i).getItem();
|
||||
// Default to the standard spell book texture, which will always be the first item in the list
|
||||
s = s.withProperty(BOOKS[i], bookItems.contains(item) ? bookItems.indexOf(item) : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int meta){
|
||||
return new TileEntityBookshelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand,
|
||||
EnumFacing side, float hitX, float hitY, float hitZ){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity == null || player.isSneaking()){
|
||||
return false;
|
||||
}
|
||||
|
||||
player.openGui(Wizardry.instance, WizardryGuiHandler.BOOKSHELF, world, pos.getX(), pos.getY(), pos.getZ());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasComparatorInputOverride(IBlockState state){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getComparatorInputOverride(IBlockState state, World world, BlockPos pos){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity instanceof TileEntityBookshelf){
|
||||
|
||||
int slotsOccupied = 0;
|
||||
|
||||
for(int i = 0; i < ((TileEntityBookshelf)tileEntity).getSizeInventory(); i++){
|
||||
if(!((TileEntityBookshelf)tileEntity).getStackInSlot(i).isEmpty()) slotsOccupied++;
|
||||
}
|
||||
|
||||
return slotsOccupied;
|
||||
|
||||
}
|
||||
|
||||
return super.getComparatorInputOverride(state, world, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eventReceived(IBlockState state, World world, BlockPos pos, int id, int param){
|
||||
super.eventReceived(state, world, pos, id, param);
|
||||
TileEntity tileentity = world.getTileEntity(pos);
|
||||
return tileentity != null && tileentity.receiveClientEvent(id, param);
|
||||
}
|
||||
|
||||
/** Returns the list of book textures, in ID order. */
|
||||
public static ImmutableList<ResourceLocation> getBookTextures(){
|
||||
// ModelBookshelf calls this before init() so we need to return the map values as a fallback
|
||||
return bookTextures == null ? ImmutableList.copyOf(BOOK_TEXTURE_MAP.values()) : bookTextures;
|
||||
}
|
||||
|
||||
/** Returns the list of registered book items, in ID order. */
|
||||
public static ImmutableList<Item> getBookItems(){
|
||||
return bookItems;
|
||||
}
|
||||
|
||||
/** Called during block registration to initialise the block properties for each book. */
|
||||
public static void initBookProperties(){
|
||||
for(int i=0; i<SLOT_COUNT; i++){
|
||||
BOOKS[i] = new UnlistedPropertyInt("book" + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Based on BlockFluid's UnlistedPropertyBool; this is only needed because of Java's weird restrictions on generics
|
||||
private static final class UnlistedPropertyInt extends Properties.PropertyAdapter<Integer> {
|
||||
|
||||
public UnlistedPropertyInt(String name){
|
||||
// Max is inclusive here, but we're using the largest value for no book so there's an extra one (can't use -1)
|
||||
super(PropertyInteger.create(name, 0, BOOK_TEXTURE_MAP.size()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Called from {@link Wizardry#init(FMLInitializationEvent)} to retrieve the actual book items and compile them
|
||||
* and their textures into immutable lists. */
|
||||
public static void compileBookModelTextures(){
|
||||
|
||||
ImmutableList.Builder<Item> itemListBuider = ImmutableList.builder();
|
||||
ImmutableList.Builder<ResourceLocation> textureListBuilder = ImmutableList.builder();
|
||||
|
||||
for(Map.Entry<Supplier<Item>, ResourceLocation> entry : BOOK_TEXTURE_MAP.entrySet()){
|
||||
itemListBuider.add(entry.getKey().get());
|
||||
textureListBuilder.add(entry.getValue());
|
||||
}
|
||||
|
||||
bookItems = itemListBuider.build();
|
||||
bookTextures = textureListBuilder.build();
|
||||
}
|
||||
|
||||
/** Called from {@link Wizardry#preInit(FMLPreInitializationEvent)} to register the default set of book textures. */
|
||||
public static void registerStandardBookModelTextures(){
|
||||
// Vanilla Minecraft books
|
||||
// Regular brown books are the default so they're registered first
|
||||
registerBookModelTexture(() -> Items.BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
|
||||
registerBookModelTexture(() -> Items.WRITABLE_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
|
||||
registerBookModelTexture(() -> Items.WRITTEN_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
|
||||
registerBookModelTexture(() -> Items.ENCHANTED_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_enchanted"));
|
||||
// Wizardry books
|
||||
registerBookModelTexture(() -> WizardryItems.spell_book, new ResourceLocation(Wizardry.MODID, "blocks/books_red"));
|
||||
registerBookModelTexture(() -> WizardryItems.wizard_handbook, new ResourceLocation(Wizardry.MODID, "blocks/books_blue"));
|
||||
registerBookModelTexture(() -> WizardryItems.arcane_tome, new ResourceLocation(Wizardry.MODID, "blocks/books_purple"));
|
||||
registerBookModelTexture(() -> WizardryItems.ruined_spell_book, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
|
||||
registerBookModelTexture(() -> WizardryItems.scroll, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_blue"));
|
||||
registerBookModelTexture(() -> WizardryItems.blank_scroll, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_blue"));
|
||||
registerBookModelTexture(() -> WizardryItems.identification_scroll, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_purple"));
|
||||
// Can't use the map in WandHelper because this is called from preInit (addons will have to add theirs manually)
|
||||
registerBookModelTexture(() -> WizardryItems.storage_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.siphon_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.condenser_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.range_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.duration_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.cooldown_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.blast_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.attunement_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
registerBookModelTexture(() -> WizardryItems.melee_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a book texture to be used for the book model when the given item is placed in a bookshelf. This method
|
||||
* <b>must</b> be called from {@code preInit} as the registered item count is required during block registration.
|
||||
* This also necessitates the use of a supplier to fetch the item, since the items will not yet be registered.
|
||||
* @param itemFactory A {@link Supplier} that returns the book item to link the texture to. This item need not
|
||||
* be an instance of {@link electroblob.wizardry.item.ItemSpellBook ItemSpellBook}. Duplicate
|
||||
* items will result in an {@link IllegalArgumentException} being thrown later.
|
||||
* @param texture The texture to apply to the book model.
|
||||
*/
|
||||
public static void registerBookModelTexture(Supplier<Item> itemFactory, ResourceLocation texture){
|
||||
BOOK_TEXTURE_MAP.put(itemFactory, texture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of nearby bookshelves' inventories, where 'bookshelves' are any tile entities with inventories
|
||||
* whose blocks are specified in the config file under the {@code bookshelfBlocks} option.
|
||||
* @param world The world to search in
|
||||
* @param centre The position to search around
|
||||
* @param exclude Any tile entities that should be excluded from the returned list
|
||||
* @return A list of nearby {@link IInventory} objects that count as valid bookshelves
|
||||
*/
|
||||
public static List<IInventory> findNearbyBookshelves(World world, BlockPos centre, TileEntity... exclude){
|
||||
|
||||
List<IInventory> bookshelves = new ArrayList<>();
|
||||
|
||||
int searchRadius = Wizardry.settings.bookshelfSearchRadius;
|
||||
|
||||
for(int x = -searchRadius; x <= searchRadius; x++){
|
||||
for(int y = -searchRadius; y <= searchRadius; y++){
|
||||
for(int z = -searchRadius; z <= searchRadius; z++){
|
||||
|
||||
BlockPos pos = centre.add(x, y, z);
|
||||
|
||||
if(Settings.containsMetaBlock(Wizardry.settings.bookshelfBlocks, world.getBlockState(pos))){
|
||||
TileEntity te = world.getTileEntity(pos);
|
||||
if(te instanceof IInventory && !ArrayUtils.contains(exclude, te)) bookshelves.add((IInventory)te);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bookshelves;
|
||||
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWorldLoadEvent(WorldEvent.Load event){
|
||||
event.getWorld().addEventListener(Listener.instance);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWorldUnloadEvent(WorldEvent.Unload event){
|
||||
event.getWorld().removeEventListener(Listener.instance);
|
||||
}
|
||||
|
||||
public static class Listener implements IWorldEventListener {
|
||||
|
||||
public static final Listener instance = new Listener();
|
||||
|
||||
private Listener(){}
|
||||
|
||||
@Override
|
||||
public void notifyBlockUpdate(World world, BlockPos pos, IBlockState oldState, IBlockState newState, int flags){
|
||||
|
||||
if(oldState == newState) return; // Probably won't happen but just in case
|
||||
|
||||
if(Settings.containsMetaBlock(Wizardry.settings.bookshelfBlocks, oldState) // Bookshelf removed
|
||||
|| Settings.containsMetaBlock(Wizardry.settings.bookshelfBlocks, newState)){ // Bookshelf placed
|
||||
// It is also possible (with commands) for a bookshelf to be replaced with another bookshelf, in which
|
||||
// case this should still just be called once
|
||||
Wizardry.proxy.notifyBookshelfChange(world, pos);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Dummy implementations
|
||||
@Override public void notifyLightSet(BlockPos pos){}
|
||||
@Override public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2){}
|
||||
@Override public void playSoundToAllNearExcept(@Nullable EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, float pitch){}
|
||||
@Override public void playRecord(SoundEvent soundIn, BlockPos pos){}
|
||||
@Override public void spawnParticle(int particleID, boolean ignoreRange, double xCoord, double yCoord, double zCoord, double xSpeed, double ySpeed, double zSpeed, int... parameters){}
|
||||
@Override public void spawnParticle(int id, boolean ignoreRange, boolean minimiseParticleLevel, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, int... parameters){}
|
||||
@Override public void onEntityAdded(Entity entityIn){}
|
||||
@Override public void onEntityRemoved(Entity entityIn){}
|
||||
@Override public void broadcastSound(int soundID, BlockPos pos, int data){}
|
||||
@Override public void playEvent(EntityPlayer player, int type, BlockPos blockPosIn, int data){}
|
||||
@Override public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress){}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,9 @@
|
||||
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 +18,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 +42,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ public class BlockCrystalFlower extends BlockBush {
|
||||
@SubscribeEvent
|
||||
public static void onBonemealEvent(BonemealEvent event){
|
||||
// Grows crystal flowers when bonemeal is used on grass
|
||||
if(event.getBlock().getBlock() == Blocks.GRASS){
|
||||
if(Wizardry.settings.bonemealGrowsCrystalFlowers && event.getBlock().getBlock() == Blocks.GRASS){
|
||||
|
||||
BlockPos pos = event.getPos().add(event.getWorld().rand.nextInt(8) - event.getWorld().rand.nextInt(8),
|
||||
event.getWorld().rand.nextInt(4) - event.getWorld().rand.nextInt(4),
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.BlockFaceShape;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityFlowerPot;
|
||||
import net.minecraft.util.BlockRenderLayer;
|
||||
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.util.math.RayTraceResult;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Random;
|
||||
|
||||
public class BlockCrystalFlowerPot extends Block {
|
||||
|
||||
protected static final AxisAlignedBB BOUNDING_BOX = new AxisAlignedBB(0.3125D, 0.0D, 0.3125D, 0.6875D, 0.375D, 0.6875D);
|
||||
|
||||
public BlockCrystalFlowerPot(){
|
||||
super(Material.CIRCUITS);
|
||||
this.setLightLevel(0.4f);
|
||||
this.setTickRandomly(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random){
|
||||
if(world.isRemote && random.nextBoolean()){
|
||||
ParticleBuilder.create(Type.SPARKLE)
|
||||
.pos(pos.getX() + 0.3 + random.nextDouble() * 0.4, pos.getY() + 0.6 + random.nextDouble() * 0.3, pos.getZ() + 0.3 + random.nextDouble() * 0.4)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TileEntity createTileEntity(World world, IBlockState state){
|
||||
return new TileEntityFlowerPot(Item.getItemFromBlock(WizardryBlocks.crystal_flower), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTileEntity(IBlockState state){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){
|
||||
|
||||
ItemStack stack = new ItemStack(WizardryBlocks.crystal_flower);
|
||||
|
||||
if(player.getHeldItem(hand).isEmpty()){
|
||||
player.setHeldItem(hand, stack);
|
||||
}else if(!player.addItemStackToInventory(stack)){
|
||||
player.dropItem(stack, false);
|
||||
}
|
||||
|
||||
world.setBlockState(pos, Blocks.FLOWER_POT.getDefaultState());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getPickBlock(IBlockState state, RayTraceResult target, World world, BlockPos pos, EntityPlayer player){
|
||||
return new ItemStack(WizardryBlocks.crystal_flower);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDrops(net.minecraft.util.NonNullList<ItemStack> drops, IBlockAccess world, BlockPos pos, IBlockState state, int fortune){
|
||||
super.getDrops(drops, world, pos, state, fortune);
|
||||
drops.add(new ItemStack(WizardryBlocks.crystal_flower));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItemDropped(IBlockState state, Random rand, int fortune){
|
||||
return Items.FLOWER_POT;
|
||||
}
|
||||
|
||||
// The rest are identical behaviour to BlockFlowerPot
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
|
||||
return BOUNDING_BOX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceBlockAt(World world, BlockPos pos){
|
||||
IBlockState downState = world.getBlockState(pos.down());
|
||||
return super.canPlaceBlockAt(world, pos) && (downState.isTopSolid() || downState.getBlockFaceShape(world, pos.down(), EnumFacing.UP) == BlockFaceShape.SOLID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos neighbour){
|
||||
IBlockState downState = world.getBlockState(pos.down());
|
||||
if(!downState.isTopSolid() && downState.getBlockFaceShape(world, pos.down(), EnumFacing.UP) != BlockFaceShape.SOLID){
|
||||
this.dropBlockAsItem(world, pos, state, 0);
|
||||
world.setBlockToAir(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockRenderLayer getRenderLayer(){
|
||||
return BlockRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockFaceShape getBlockFaceShape(IBlockAccess worldIn, IBlockState state, BlockPos pos, EnumFacing face){
|
||||
return BlockFaceShape.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest){
|
||||
if(willHarvest) return true; // If it will harvest, delay deletion of the block until after getDrops
|
||||
return super.removedByPlayer(state, world, pos, player, willHarvest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack tool){
|
||||
super.harvestBlock(world, player, pos, state, te, tool);
|
||||
world.setBlockToAir(pos);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,41 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import net.minecraft.block.BlockFrostedIce;
|
||||
import net.minecraft.block.SoundType;
|
||||
import net.minecraft.block.material.Material;
|
||||
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 {
|
||||
|
||||
public BlockDryFrostedIce(){
|
||||
super();
|
||||
setDefaultSlipperiness(0.98f); // For some strange reason BlockFrostedIce overrides BlockIce's icy-ness...
|
||||
setSoundType(SoundType.GLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material getMaterial(IBlockState state){
|
||||
return Material.ICE; // For goodness sake
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import electroblob.wizardry.registry.WizardryTabs;
|
||||
import net.minecraft.block.BlockPlanks;
|
||||
import net.minecraft.block.SoundType;
|
||||
|
||||
public class BlockGildedWood extends BlockPlanks {
|
||||
|
||||
public BlockGildedWood(){
|
||||
super();
|
||||
this.setCreativeTab(WizardryTabs.WIZARDRY);
|
||||
this.setHardness(2.0F);
|
||||
this.setResistance(5.0F);
|
||||
this.setSoundType(SoundType.WOOD); // Why is this protected?!
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.registry.WizardryTabs;
|
||||
import electroblob.wizardry.tileentity.TileEntityImbuementAltar;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.ITileEntityProvider;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.properties.PropertyBool;
|
||||
import net.minecraft.block.state.BlockFaceShape;
|
||||
import net.minecraft.block.state.BlockStateContainer;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.InventoryHelper;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.BlockRenderLayer;
|
||||
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;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BlockImbuementAltar extends Block implements ITileEntityProvider {
|
||||
|
||||
public static final PropertyBool ACTIVE = PropertyBool.create("active");
|
||||
|
||||
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0, 0.0, 0.0, 1.0, 0.75, 1.0);
|
||||
|
||||
public BlockImbuementAltar(){
|
||||
super(Material.ROCK);
|
||||
this.setBlockUnbreakable();
|
||||
this.setResistance(6000000);
|
||||
this.setLightLevel(0.4f);
|
||||
this.setCreativeTab(WizardryTabs.WIZARDRY);
|
||||
this.setDefaultState(this.blockState.getBaseState().withProperty(ACTIVE, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockStateContainer createBlockState(){
|
||||
return new BlockStateContainer(this, ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getStateFromMeta(int meta){
|
||||
return this.getDefaultState().withProperty(ACTIVE, meta == 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetaFromState(IBlockState state){
|
||||
return state.getValue(ACTIVE) ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
|
||||
return AABB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int metadata){
|
||||
return new TileEntityImbuementAltar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumBlockRenderType getRenderType(IBlockState state){
|
||||
return EnumBlockRenderType.MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockRenderLayer getRenderLayer(){
|
||||
return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueCube(IBlockState state){
|
||||
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 int getLightValue(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
return state.getValue(ACTIVE) ? super.getLightValue(state, world, pos) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos neighbour){
|
||||
|
||||
boolean shouldBeActive = Arrays.stream(EnumFacing.HORIZONTALS)
|
||||
.allMatch(s -> world.getBlockState(pos.offset(s)).getBlock() == WizardryBlocks.receptacle
|
||||
&& world.getBlockState(pos.offset(s)).getValue(BlockReceptacle.FACING) == s);
|
||||
|
||||
if(world.getBlockState(pos).getValue(ACTIVE) != shouldBeActive){ // Only set when it actually needs changing
|
||||
|
||||
// Get contents of altar before replacing it
|
||||
TileEntity te = world.getTileEntity(pos);
|
||||
ItemStack stack = ItemStack.EMPTY;
|
||||
if(te instanceof TileEntityImbuementAltar) stack = ((TileEntityImbuementAltar)te).getStack();
|
||||
|
||||
world.setBlockState(pos, world.getBlockState(pos).withProperty(ACTIVE, shouldBeActive));
|
||||
|
||||
// Copy over contents of altar from before to new tile entity
|
||||
te = world.getTileEntity(pos);
|
||||
if(te instanceof TileEntityImbuementAltar) ((TileEntityImbuementAltar)te).setStack(stack);
|
||||
|
||||
world.checkLight(pos);
|
||||
}
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
if(tileEntity instanceof TileEntityImbuementAltar){
|
||||
((TileEntityImbuementAltar)tileEntity).checkRecipe();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand,
|
||||
EnumFacing side, float hitX, float hitY, float hitZ){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(!(tileEntity instanceof TileEntityImbuementAltar) || player.isSneaking()){
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemStack currentStack = ((TileEntityImbuementAltar)tileEntity).getStack();
|
||||
ItemStack toInsert = player.getHeldItem(hand);
|
||||
|
||||
if(currentStack.isEmpty()){
|
||||
ItemStack stack = toInsert.copy();
|
||||
stack.setCount(1);
|
||||
((TileEntityImbuementAltar)tileEntity).setStack(stack);
|
||||
((TileEntityImbuementAltar)tileEntity).setLastUser(player);
|
||||
if(!player.isCreative()) toInsert.shrink(1);
|
||||
|
||||
}else{
|
||||
|
||||
if(toInsert.isEmpty()){
|
||||
player.setHeldItem(hand, currentStack);
|
||||
}else if(!player.addItemStackToInventory(currentStack)){
|
||||
player.dropItem(currentStack, false);
|
||||
}
|
||||
|
||||
((TileEntityImbuementAltar)tileEntity).setStack(ItemStack.EMPTY);
|
||||
((TileEntityImbuementAltar)tileEntity).setLastUser(null);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void breakBlock(World world, BlockPos pos, IBlockState block){
|
||||
|
||||
TileEntity tileentity = world.getTileEntity(pos);
|
||||
|
||||
if(tileentity instanceof TileEntityImbuementAltar){
|
||||
InventoryHelper.spawnItemStack(world, pos.getX(), pos.getY(), pos.getZ(), ((TileEntityImbuementAltar)tileentity).getStack());
|
||||
}
|
||||
|
||||
super.breakBlock(world, pos, block);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.WizardryGuiHandler;
|
||||
import electroblob.wizardry.registry.WizardryTabs;
|
||||
import electroblob.wizardry.tileentity.TileEntityLectern;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.block.BlockHorizontal;
|
||||
import net.minecraft.block.ITileEntityProvider;
|
||||
import net.minecraft.block.SoundType;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.BlockFaceShape;
|
||||
import net.minecraft.block.state.BlockStateContainer;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.Mirror;
|
||||
import net.minecraft.util.Rotation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Random;
|
||||
|
||||
public class BlockLectern extends BlockHorizontal implements ITileEntityProvider {
|
||||
|
||||
public BlockLectern(){
|
||||
super(Material.WOOD);
|
||||
this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH));
|
||||
this.setCreativeTab(WizardryTabs.WIZARDRY);
|
||||
this.setHardness(2.0F);
|
||||
this.setResistance(5.0F);
|
||||
this.setSoundType(SoundType.WOOD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockStateContainer createBlockState(){
|
||||
return new BlockStateContainer.Builder(this).add(FACING).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getStateFromMeta(int meta){
|
||||
EnumFacing enumfacing = EnumFacing.byIndex(meta);
|
||||
if(enumfacing.getAxis() == EnumFacing.Axis.Y) enumfacing = EnumFacing.NORTH;
|
||||
return this.getDefaultState().withProperty(FACING, enumfacing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetaFromState(IBlockState state){
|
||||
return state.getValue(FACING).getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random rand){
|
||||
|
||||
EntityPlayer entityplayer = world.getClosestPlayer(pos.getX() + 0.5, pos.getY() + 0.5,
|
||||
pos.getZ() + 0.5, TileEntityLectern.BOOK_OPEN_DISTANCE, false);
|
||||
|
||||
if(entityplayer != null){
|
||||
ParticleBuilder.create(Type.DUST).pos(pos.getX() + rand.nextFloat(), pos.getY() + 1, pos.getZ() + rand.nextFloat())
|
||||
.vel(0, 0.03, 0).clr(1, 1, 0.65f).fade(0.7f, 0, 1).shaded(false).spawn(world);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullBlock(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNormalCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceTorchOnTop(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){
|
||||
return face == EnumFacing.DOWN ? BlockFaceShape.SOLID : BlockFaceShape.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer){
|
||||
return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState withRotation(IBlockState state, Rotation rotation){
|
||||
return state.withProperty(FACING, rotation.rotate(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState withMirror(IBlockState state, Mirror mirror){
|
||||
return state.withRotation(mirror.toRotation(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int meta){
|
||||
return new TileEntityLectern();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand,
|
||||
EnumFacing side, float hitX, float hitY, float hitZ){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity == null || player.isSneaking()){
|
||||
return false;
|
||||
}
|
||||
|
||||
player.openGui(Wizardry.instance, WizardryGuiHandler.LECTERN, world, pos.getX(), pos.getY(), pos.getZ());
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,125 @@
|
||||
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();
|
||||
}
|
||||
Element element = Element.values()[metadata];
|
||||
if(!ELEMENT.getAllowedValues().contains(element)) return this.getDefaultState().withProperty(NATURAL, natural);
|
||||
return this.getDefaultState().withProperty(ELEMENT, element).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,91 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.EntityUtils;
|
||||
import net.minecraft.block.state.BlockFaceShape;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class BlockPermafrost extends BlockDryFrostedIce {
|
||||
|
||||
protected static final AxisAlignedBB BOUNDING_BOX = new AxisAlignedBB(0, 0, 0, 1, 0, 1);
|
||||
protected static final AxisAlignedBB SELECTION_BOUNDING_BOX = new AxisAlignedBB(0, 0, 0, 1, 0.125, 1);
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
|
||||
return BOUNDING_BOX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getSelectedBoundingBox(IBlockState state, World worldIn, BlockPos pos){
|
||||
return SELECTION_BOUNDING_BOX;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AxisAlignedBB getCollisionBoundingBox(IBlockState blockState, IBlockAccess worldIn, BlockPos pos){
|
||||
return super.getCollisionBoundingBox(blockState, worldIn, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNormalCube(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 void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){
|
||||
|
||||
if(EntityUtils.isLiving(entity) && entity.ticksExisted % 30 == 0){
|
||||
// Can't make it player damage unless we make this block a tile entity, but there will be too many for that
|
||||
entity.attackEntityFrom(DamageSource.MAGIC, Spells.permafrost.getProperty(Spell.DAMAGE).floatValue());
|
||||
int duration = Spells.permafrost.getProperty(Spell.EFFECT_DURATION).intValue();
|
||||
int amplifier = Spells.permafrost.getProperty(Spell.EFFECT_STRENGTH).intValue();
|
||||
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, duration, amplifier));
|
||||
}
|
||||
|
||||
// EntityLivingBase's slipperiness code doesn't get the block below it properly so slipperiness only works for
|
||||
// full blocks...
|
||||
if(entity.onGround){
|
||||
|
||||
// Not brilliant but it's about the best I can do
|
||||
entity.motionX *= 1.12 - entity.motionX * entity.motionX;
|
||||
entity.motionZ *= 1.12 - entity.motionZ * entity.motionZ;
|
||||
|
||||
// if(entity instanceof EntityLivingBase){
|
||||
// double maxVel = 0.8;
|
||||
// double x = entity.motionX;
|
||||
// double y = entity.motionY;
|
||||
// double z = entity.motionZ;
|
||||
// double vel = MathHelper.sqrt(x*x + y*y + z*z);
|
||||
// double m = vel / maxVel;
|
||||
// if(m > 1){
|
||||
// entity.motionX /= m;
|
||||
// entity.motionZ /= m;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.item.ItemSpectralDust;
|
||||
import electroblob.wizardry.registry.*;
|
||||
import electroblob.wizardry.tileentity.TileEntityReceptacle;
|
||||
import electroblob.wizardry.util.GeometryUtils;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockTorch;
|
||||
import net.minecraft.block.ITileEntityProvider;
|
||||
import net.minecraft.block.SoundType;
|
||||
import net.minecraft.block.state.BlockFaceShape;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.NonNullList;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class BlockReceptacle extends BlockTorch implements ITileEntityProvider {
|
||||
|
||||
protected static final AxisAlignedBB STANDING_AABB = new AxisAlignedBB(4 / 16d, 0 / 16d, 4 / 16d, 12 / 16d, 8 / 16d, 12 / 16d);
|
||||
protected static final AxisAlignedBB NORTH_WALL_AABB = new AxisAlignedBB(4 / 16d, 2 / 16d, 7 / 16d, 12 / 16d, 10 / 16d, 16 / 16d);
|
||||
protected static final AxisAlignedBB SOUTH_WALL_AABB = new AxisAlignedBB(4 / 16d, 2 / 16d, 0 / 16d, 12 / 16d, 10 / 16d, 9 / 16d);
|
||||
protected static final AxisAlignedBB WEST_WALL_AABB = new AxisAlignedBB(7 / 16d, 2 / 16d, 4 / 16d, 16 / 16d, 10 / 16d, 12 / 16d);
|
||||
protected static final AxisAlignedBB EAST_WALL_AABB = new AxisAlignedBB(0 / 16d, 2 / 16d, 4 / 16d, 9 / 16d, 10 / 16d, 12 / 16d);
|
||||
|
||||
private static final double WALL_PARTICLE_OFFSET = 3 / 16d;
|
||||
|
||||
public static final Map<Element, int[]> PARTICLE_COLOURS;
|
||||
|
||||
static{
|
||||
|
||||
Map<Element, int[]> map = Maps.newEnumMap(Element.class);
|
||||
|
||||
map.put(Element.MAGIC, new int[]{0xe4c7cd, 0xfeffbe, 0x9d2cf3});
|
||||
map.put(Element.FIRE, new int[]{0xff9600, 0xfffe67, 0xd02700});
|
||||
map.put(Element.ICE, new int[]{0xa3e8f4, 0xe9f9fc, 0x138397});
|
||||
map.put(Element.LIGHTNING, new int[]{0x409ee1, 0xf5f0ff, 0x225474});
|
||||
map.put(Element.NECROMANCY, new int[]{0xa811ce, 0xf575f5, 0x382366});
|
||||
map.put(Element.EARTH, new int[]{0xa8f408, 0xc8ffb2, 0x795c28});
|
||||
map.put(Element.SORCERY, new int[]{0x56e8e3, 0xe8fcfc, 0x16a64d});
|
||||
map.put(Element.HEALING, new int[]{0xfff69e, 0xfffff6, 0xa18200});
|
||||
|
||||
PARTICLE_COLOURS = Maps.immutableEnumMap(map);
|
||||
}
|
||||
|
||||
public BlockReceptacle(){
|
||||
super();
|
||||
this.setCreativeTab(WizardryTabs.WIZARDRY);
|
||||
this.setHardness(0);
|
||||
this.setLightLevel(0.5f);
|
||||
this.setSoundType(SoundType.STONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
switch(state.getValue(FACING)){
|
||||
case EAST:
|
||||
return EAST_WALL_AABB;
|
||||
case WEST:
|
||||
return WEST_WALL_AABB;
|
||||
case SOUTH:
|
||||
return SOUTH_WALL_AABB;
|
||||
case NORTH:
|
||||
return NORTH_WALL_AABB;
|
||||
default:
|
||||
return STANDING_AABB;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getCollisionBoundingBox(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
return state.getBoundingBox(world, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightValue(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity instanceof TileEntityReceptacle && ((TileEntityReceptacle)tileEntity).getElement() != null){
|
||||
return super.getLightValue(state, world, pos); // Return super to use float value from constructor
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDrops(NonNullList<ItemStack> drops, IBlockAccess world, BlockPos pos, IBlockState state, int fortune){
|
||||
|
||||
super.getDrops(drops, world, pos, state, fortune);
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity instanceof TileEntityReceptacle){
|
||||
Element element = ((TileEntityReceptacle)tileEntity).getElement();
|
||||
if(element != null) drops.add(new ItemStack(WizardryItems.spectral_dust, 1, element.ordinal()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceBlockOnSide(World world, BlockPos pos, EnumFacing side){
|
||||
|
||||
if(side != EnumFacing.UP && side != EnumFacing.DOWN
|
||||
&& world.getBlockState(pos.offset(side.getOpposite())).getBlock() instanceof BlockImbuementAltar){
|
||||
return true;
|
||||
}
|
||||
return super.canPlaceBlockOnSide(world, pos, side);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkForDrop(World world, BlockPos pos, IBlockState state){
|
||||
if(state.getBlock() == this && this.canPlaceAt(world, pos, state.getValue(FACING))){
|
||||
return true;
|
||||
}
|
||||
return super.checkForDrop(world, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onNeighborChangeInternal(World world, BlockPos pos, IBlockState state){
|
||||
// This is so stupid, BlockTorch DUPLICATES the code that checks for valid placement in the super method instead
|
||||
// of checking the existing methods...
|
||||
return !this.checkForDrop(world, pos, state); // Literally all we need. None of the rubbish in super.
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer){
|
||||
|
||||
if(this.canPlaceAt(world, pos, facing)){
|
||||
return this.getDefaultState().withProperty(FACING, facing);
|
||||
}else{
|
||||
|
||||
for(EnumFacing enumfacing : EnumFacing.Plane.HORIZONTAL){
|
||||
if(this.canPlaceAt(world, pos, enumfacing)){
|
||||
return this.getDefaultState().withProperty(FACING, enumfacing);
|
||||
}
|
||||
}
|
||||
|
||||
return this.getDefaultState();
|
||||
}
|
||||
}
|
||||
|
||||
// Why is everything in BlockTorch private, for goodness sake...
|
||||
private boolean canPlaceAt(World world, BlockPos pos, EnumFacing facing){
|
||||
|
||||
BlockPos blockpos = pos.offset(facing.getOpposite());
|
||||
IBlockState state = world.getBlockState(blockpos);
|
||||
Block block = state.getBlock();
|
||||
BlockFaceShape blockfaceshape = state.getBlockFaceShape(world, blockpos, facing);
|
||||
|
||||
if(facing.equals(EnumFacing.UP) && this.canPlaceOn(world, blockpos)){
|
||||
return true;
|
||||
}else if(facing != EnumFacing.UP && facing != EnumFacing.DOWN){
|
||||
return !isExceptBlockForAttachWithPiston(block) && (blockfaceshape == BlockFaceShape.SOLID || block instanceof BlockImbuementAltar);
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canPlaceOn(World world, BlockPos pos){
|
||||
IBlockState state = world.getBlockState(pos);
|
||||
return state.getBlock().canPlaceTorchOnTop(state, world, pos);
|
||||
}
|
||||
|
||||
// See BlockFlowerPot for these two (this class is essentially based on a flower pot)
|
||||
|
||||
@Override
|
||||
public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest){
|
||||
if(willHarvest) return true; // If it will harvest, delay deletion of the block until after getDrops
|
||||
return super.removedByPlayer(state, world, pos, player, willHarvest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack tool){
|
||||
super.harvestBlock(world, player, pos, state, te, tool);
|
||||
world.setBlockToAir(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
ItemStack stack = player.getHeldItem(hand);
|
||||
|
||||
if(tileEntity instanceof TileEntityReceptacle){
|
||||
|
||||
Element currentElement = ((TileEntityReceptacle)tileEntity).getElement();
|
||||
|
||||
if(currentElement == null){
|
||||
|
||||
if(stack.getItem() instanceof ItemSpectralDust && stack.getMetadata() >= 0
|
||||
&& stack.getMetadata() < Element.values().length){
|
||||
|
||||
((TileEntityReceptacle)tileEntity).setElement(Element.values()[stack.getMetadata()]);
|
||||
if(!player.capabilities.isCreativeMode) stack.shrink(1);
|
||||
world.playSound(pos.getX(), pos.getY(), pos.getZ(), WizardrySounds.BLOCK_RECEPTACLE_IGNITE,
|
||||
SoundCategory.BLOCKS, 0.7f, 0.7f, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
((TileEntityReceptacle)tileEntity).setElement(null);
|
||||
|
||||
ItemStack dust = new ItemStack(WizardryItems.spectral_dust, 1, currentElement.ordinal());
|
||||
|
||||
if(stack.isEmpty()){
|
||||
player.setHeldItem(hand, dust);
|
||||
}else if(!player.addItemStackToInventory(dust)){
|
||||
player.dropItem(dust, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random rand){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity instanceof TileEntityReceptacle){
|
||||
|
||||
Element element = ((TileEntityReceptacle)tileEntity).getElement();
|
||||
|
||||
if(element != null){
|
||||
|
||||
EnumFacing facing = state.getValue(FACING).getOpposite();
|
||||
|
||||
Vec3d centre = GeometryUtils.getCentre(pos);
|
||||
if(facing.getAxis().isHorizontal()){
|
||||
centre = centre.add(new Vec3d(facing.getDirectionVec()).scale(WALL_PARTICLE_OFFSET)).add(0, 0.125, 0);
|
||||
}
|
||||
|
||||
int[] colours = PARTICLE_COLOURS.get(element);
|
||||
|
||||
ParticleBuilder.create(ParticleBuilder.Type.FLASH).pos(centre).scale(0.35f).time(48).clr(colours[0]).spawn(world);
|
||||
|
||||
double r = 0.12;
|
||||
|
||||
for(int i = 0; i < 3; i++){
|
||||
|
||||
double x = r * (rand.nextDouble() * 2 - 1);
|
||||
double y = r * (rand.nextDouble() * 2 - 1);
|
||||
double z = r * (rand.nextDouble() * 2 - 1);
|
||||
|
||||
ParticleBuilder.create(ParticleBuilder.Type.DUST).pos(centre.x + x, centre.y + y, centre.z + z)
|
||||
.vel(x * -0.03, 0.02, z * -0.03).time(24 + rand.nextInt(8)).clr(colours[1]).fade(colours[2]).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack){
|
||||
if(placer instanceof EntityPlayer){
|
||||
BlockPos centre = pos.offset(world.getBlockState(pos).getValue(FACING).getOpposite());
|
||||
if(world.getBlockState(centre).getBlock() == WizardryBlocks.imbuement_altar
|
||||
&& Arrays.stream(EnumFacing.HORIZONTALS).allMatch(f -> world.getBlockState(centre.offset(f)).getBlock() == WizardryBlocks.receptacle)){
|
||||
WizardryAdvancementTriggers.restore_imbuement_altar.triggerFor((EntityPlayer)placer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int meta){
|
||||
return new TileEntityReceptacle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasComparatorInputOverride(IBlockState state){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getComparatorInputOverride(IBlockState state, World world, BlockPos pos){
|
||||
|
||||
TileEntity tileEntity = world.getTileEntity(pos);
|
||||
|
||||
if(tileEntity instanceof TileEntityReceptacle){
|
||||
Element element = ((TileEntityReceptacle)tileEntity).getElement();
|
||||
return element == null ? 0 : element.ordinal() + 1;
|
||||
}
|
||||
|
||||
return super.getComparatorInputOverride(state, world, pos);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
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){
|
||||
Element element = Element.values()[metadata];
|
||||
if(!ELEMENT.getAllowedValues().contains(element)) return this.getDefaultState();
|
||||
return this.getDefaultState().withProperty(ELEMENT, element);
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -18,6 +18,7 @@ import net.minecraft.item.Item;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.BlockRenderLayer;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumBlockRenderType;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
@@ -25,8 +26,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,19 +53,26 @@ 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(entity instanceof EntityLivingBase){
|
||||
|
||||
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)){
|
||||
|
||||
world.destroyBlock(pos, false);
|
||||
DamageSource source = tileentity.getCaster() == null ? DamageSource.CACTUS
|
||||
: MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC);
|
||||
|
||||
entity.attackEntityFrom(source, Spells.snare.getProperty(Spell.DAMAGE).floatValue());
|
||||
|
||||
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS,
|
||||
Spells.snare.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.snare.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
|
||||
if(!world.isRemote) world.destroyBlock(pos, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +89,7 @@ public class BlockSnare extends BlockContainer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockRenderLayer getBlockLayer(){
|
||||
public BlockRenderLayer getRenderLayer() {
|
||||
return BlockRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
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;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.BlockRenderLayer;
|
||||
import net.minecraft.util.EnumBlockRenderType;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
@@ -24,10 +21,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,50 +33,39 @@ public class BlockSpectral extends BlockContainer {
|
||||
|
||||
// Replaces getRenderBlockPass
|
||||
@Override
|
||||
public BlockRenderLayer getBlockLayer(){
|
||||
@SideOnly(Side.CLIENT)
|
||||
public BlockRenderLayer getRenderLayer(){
|
||||
return BlockRenderLayer.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumBlockRenderType getRenderType(IBlockState state){
|
||||
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'.
|
||||
@Override
|
||||
public boolean isOpaqueCube(IBlockState state){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
return false;
|
||||
}
|
||||
// See BlockTransportationStone for what all these do
|
||||
@Override public boolean isFullCube(IBlockState state){ return false; }
|
||||
@Override public boolean isBlockNormalCube(IBlockState state){ return false; }
|
||||
@Override public boolean isNormalCube(IBlockState state){ return false; }
|
||||
@Override public boolean isOpaqueCube(IBlockState state){ return false; }
|
||||
|
||||
@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
|
||||
// full light.
|
||||
// // Overriden to make the block always look full brightness despite not emitting
|
||||
// // full light.
|
||||
// @Override
|
||||
// public int getPackedLightmapCoords(IBlockState state, IBlockAccess source, BlockPos pos){
|
||||
// return 15;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public int getPackedLightmapCoords(IBlockState state, IBlockAccess source, BlockPos pos){
|
||||
return 15;
|
||||
public boolean hasTileEntity(IBlockState state){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import electroblob.wizardry.spell.Petrify;
|
||||
import electroblob.wizardry.tileentity.TileEntityStatue;
|
||||
import electroblob.wizardry.util.BlockUtils;
|
||||
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.entity.EntityLivingBase;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.BlockRenderLayer;
|
||||
import net.minecraft.util.EnumBlockRenderType;
|
||||
@@ -20,10 +20,18 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class BlockStatue extends BlockContainer {
|
||||
import javax.annotation.Nullable;
|
||||
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 +105,7 @@ public class BlockStatue extends BlockContainer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockRenderLayer getBlockLayer(){
|
||||
public BlockRenderLayer getRenderLayer(){
|
||||
return this.isIce ? BlockRenderLayer.TRANSLUCENT : BlockRenderLayer.SOLID;
|
||||
}
|
||||
|
||||
@@ -106,6 +114,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 +159,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 +179,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 target The entity to turn into a statue.
|
||||
* @param caster The entity that caused it, or null if it was not caused by an entity
|
||||
* @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 target, @Nullable EntityLivingBase caster, int duration){
|
||||
|
||||
if(target.deathTime > 0) return false;
|
||||
|
||||
BlockPos pos = new BlockPos(target);
|
||||
World world = target.world;
|
||||
|
||||
target.hurtTime = 0; // Stops the entity looking red while frozen and the resulting z-fighting
|
||||
target.extinguish();
|
||||
|
||||
// Short mobs such as spiders and pigs
|
||||
if((target.height < 1.2 || target.isChild()) && BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canPlaceBlock(caster, world, pos)){
|
||||
|
||||
world.setBlockState(pos, this.getDefaultState());
|
||||
if(world.getTileEntity(pos) instanceof TileEntityStatue){
|
||||
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 1);
|
||||
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration);
|
||||
}
|
||||
|
||||
target.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
|
||||
target.setDead();
|
||||
return true;
|
||||
}
|
||||
// Normal sized mobs like zombies and skeletons
|
||||
else if(target.height < 2.5 && BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canBlockBeReplaced(world, pos.up())
|
||||
&& BlockUtils.canPlaceBlock(caster, world, pos) && BlockUtils.canPlaceBlock(caster, world, pos.up())){
|
||||
|
||||
world.setBlockState(pos, this.getDefaultState());
|
||||
if(world.getTileEntity(pos) instanceof TileEntityStatue){
|
||||
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 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(target, 2, 2);
|
||||
}
|
||||
|
||||
target.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
|
||||
target.setDead();
|
||||
return true;
|
||||
}
|
||||
// Tall mobs like endermen
|
||||
else if(BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canBlockBeReplaced(world, pos.up()) && BlockUtils.canBlockBeReplaced(world, pos.up(2))
|
||||
&& BlockUtils.canPlaceBlock(caster, world, pos) && BlockUtils.canPlaceBlock(caster, world, pos.up()) && BlockUtils.canPlaceBlock(caster, world, pos.up(2))){
|
||||
|
||||
world.setBlockState(pos, this.getDefaultState());
|
||||
if(world.getTileEntity(pos) instanceof TileEntityStatue){
|
||||
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 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(target, 2, 3);
|
||||
}
|
||||
|
||||
world.setBlockState(pos.up(2), this.getDefaultState());
|
||||
if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){
|
||||
((TileEntityStatue)world.getTileEntity(pos.up(2))).setCreatureAndPart(target, 3, 3);
|
||||
}
|
||||
|
||||
target.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
|
||||
target.setDead();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package electroblob.wizardry.block;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.tileentity.TileEntityThorns;
|
||||
import electroblob.wizardry.util.AllyDesignationSystem;
|
||||
import electroblob.wizardry.util.EntityUtils;
|
||||
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.ChunkCache;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
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 int GROWTH_STAGE_DURATION = 2;
|
||||
|
||||
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, meta == 0 ? EnumBlockHalf.LOWER : EnumBlockHalf.UPPER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetaFromState(IBlockState state){
|
||||
return state.getValue(HALF).ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
|
||||
if(state.getValue(HALF) == EnumBlockHalf.UPPER) pos = pos.down();
|
||||
// Copied from BlockFlowerPot on authority of the Forge docs, which say it needs to be here
|
||||
TileEntity tileentity = world instanceof ChunkCache ? ((ChunkCache)world).getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK) : world.getTileEntity(pos);
|
||||
|
||||
if(tileentity instanceof TileEntityThorns){
|
||||
return state.withProperty(AGE, ((TileEntityThorns)tileentity).getAge());
|
||||
}else{
|
||||
return state.withProperty(AGE, 7);
|
||||
}
|
||||
}
|
||||
|
||||
@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 void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){
|
||||
if(applyThornDamage(world, pos, state, entity)){
|
||||
entity.setInWeb(); // Needs to be called client-side for players (and besides, all of this is common code)
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean applyThornDamage(World world, BlockPos pos, IBlockState state, Entity target){
|
||||
|
||||
DamageSource source = DamageSource.CACTUS;
|
||||
float damage = Spells.forest_of_thorns.getProperty(Spell.DAMAGE).floatValue();
|
||||
|
||||
TileEntity tileentity = world.getTileEntity(state.getValue(HALF) == EnumBlockHalf.UPPER ? pos.down() : pos);
|
||||
|
||||
if(tileentity instanceof TileEntityThorns){
|
||||
|
||||
damage *= ((TileEntityThorns)tileentity).damageMultiplier;
|
||||
|
||||
EntityLivingBase caster = ((TileEntityThorns)tileentity).getCaster();
|
||||
|
||||
if(!AllyDesignationSystem.isValidTarget(caster, target)) return false; // Don't attack or slow allies of the caster
|
||||
|
||||
if(caster != null){
|
||||
source = MagicDamage.causeDirectMagicDamage(caster, MagicDamage.DamageType.MAGIC);
|
||||
}
|
||||
}
|
||||
|
||||
if(target.ticksExisted % 20 == 0){
|
||||
EntityUtils.attackEntityWithoutKnockback(target, source, damage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block.EnumOffsetType getOffsetType(){
|
||||
return Block.EnumOffsetType.XZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int metadata){
|
||||
return new TileEntityThorns();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTileEntity(IBlockState state){
|
||||
return state.getValue(HALF) == EnumBlockHalf.LOWER;
|
||||
}
|
||||
|
||||
@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().getBlockState(event.getPos()).getBlock() == WizardryBlocks.thorns){
|
||||
applyThornDamage(event.getWorld(), event.getPos(), event.getWorld().getBlockState(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.GeometryUtils;
|
||||
import electroblob.wizardry.util.Location;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
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,
|
||||
@@ -54,12 +62,6 @@ public class BlockTransportationStone extends Block {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Forge version of the above method. I still need to override both though because vanilla uses the other one.
|
||||
@Override
|
||||
public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos){
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... and this one is for rendering.
|
||||
@Override
|
||||
public boolean isOpaqueCube(IBlockState state){
|
||||
@@ -67,13 +69,19 @@ public class BlockTransportationStone extends Block {
|
||||
}
|
||||
|
||||
@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 +105,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,36 +114,115 @@ 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
|
||||
if(locations.isEmpty()) locations.add(here);
|
||||
else locations.set(Math.max(locations.size() - 1, 0), 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(GeometryUtils.getCentre(centre).add(x, -0.3125, z)).clr(0x86ff65)
|
||||
.time(200).scale(2).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns whether the specified location is surrounded by a complete cicle of 8 transportation stones. */
|
||||
/** Returns whether the specified location is surrounded by a complete circle of 8 transportation stones. */
|
||||
public static boolean testForCircle(World world, BlockPos pos){
|
||||
|
||||
if(world.getBlockState(pos).getMaterial().blocksMovement()) return false;
|
||||
if(world.getBlockState(pos).getMaterial().blocksMovement() || world.getBlockState(pos.up()).getMaterial()
|
||||
.blocksMovement()) return false;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
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;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Utility class containing some useful static methods for drawing GUIs, as well as general rendering.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a 'glitch' rectangle, with some rows of pixels shifted randomly to give a broken effect.
|
||||
*
|
||||
* @param random A random number generator to use
|
||||
* @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 drawGlitchRect(Random random, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, boolean flipX, boolean flipY){
|
||||
for(int i=0; i<height; i++){
|
||||
if(flipY) i = height - i - 1;
|
||||
int offset = random.nextInt(4) == 0 ? random.nextInt(6) - 3 : 0;
|
||||
drawTexturedFlippedRect(x + offset, y + i, u, v + i, width, 1, textureWidth, textureHeight, flipX, flipY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)(opacity * 0xff) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a factor between 0 and 1 that results in a smooth, aesthetically-pleasing animation when used to scale
|
||||
* things. Mainly for rendering, but can be used anywhere since it's just a mathematical formula.
|
||||
* @param lifetime The lifetime of the thing being animated, in ticks (if this is negative, disappearing is ignored)
|
||||
* @param ticksExisted The current age of the thing being animated, in ticks
|
||||
* @param partialTicks The current partial tick time
|
||||
* @param startLength The length of the appearing animation, in ticks
|
||||
* @param endLength The length of the disappearing animation, in ticks
|
||||
* @return A fraction between 0 and 1, with the value at the very start and end being 0 and the constant middle
|
||||
* section having a value of 1.
|
||||
*/
|
||||
public static float smoothScaleFactor(int lifetime, int ticksExisted, float partialTicks, int startLength, int endLength){
|
||||
float age = ticksExisted + partialTicks;
|
||||
float s = MathHelper.clamp(age < startLength || lifetime < 0 ? age/startLength : (lifetime - age) / endLength, 0, 1);
|
||||
s = (float)Math.pow(s, 0.4); // Smooths the animation
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the point with the given coordinates lies within the rectangle specified. The check is identical to
|
||||
* {@link GuiContainer#isPointInRegion(int, int, int, int, int, int)}, but without the adjustment relative to the GUI.
|
||||
* @param left The minimum x coordinate of the rectangle
|
||||
* @param top The minimum y coordinate of the rectangle
|
||||
* @param width The width of the rectangle
|
||||
* @param height The height of the rectangle
|
||||
* @param x The x coordinate of the point to test
|
||||
* @param y The y coordinate of the point to test
|
||||
* @return True if the given point is in the rectangle, false otherwise
|
||||
*/
|
||||
public static boolean isPointInRegion(int left, int top, int width, int height, int x, int y){
|
||||
return x >= left - 1 && x < left + width + 1 && y >= top - 1 && y < top + height + 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package electroblob.wizardry.client;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
||||
@EventBusSubscriber(Side.CLIENT)
|
||||
public class ScreenShakeHandler {
|
||||
|
||||
/** The remaining time for which the screen shake effect will be active. */
|
||||
private static int screenShakeCounter = 0;
|
||||
private static final float SHAKINESS = 0.5f;
|
||||
|
||||
/** Starts the client-side screen shake effect. */
|
||||
public static void shakeScreen(float intensity){
|
||||
if(Wizardry.settings.screenShake){
|
||||
screenShakeCounter = (int)(intensity / SHAKINESS);
|
||||
Minecraft.getMinecraft().player.rotationPitch -= intensity * 0.5f; // Start halfway down
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
|
||||
|
||||
if(event.player == Minecraft.getMinecraft().player && event.phase == TickEvent.Phase.END){
|
||||
|
||||
if(Wizardry.settings.screenShake){
|
||||
if(screenShakeCounter > 0){
|
||||
float magnitude = screenShakeCounter * SHAKINESS;
|
||||
Minecraft.getMinecraft().player.rotationPitch += screenShakeCounter % 2 == 0 ? magnitude : -magnitude;
|
||||
screenShakeCounter--;
|
||||
}
|
||||
}else{
|
||||
screenShakeCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,97 +1,163 @@
|
||||
package electroblob.wizardry.client;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Constants;
|
||||
import electroblob.wizardry.client.renderer.overlay.RenderBlinkEffect;
|
||||
import electroblob.wizardry.data.DispenserCastingData;
|
||||
import electroblob.wizardry.data.SpellEmitterData;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.item.ItemFlamecatcher;
|
||||
import electroblob.wizardry.item.ItemSpectralBow;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.packet.PacketControlInput;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.potion.PotionSlowTime;
|
||||
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.util.WizardryUtilities;
|
||||
import electroblob.wizardry.spell.Possession;
|
||||
import electroblob.wizardry.spell.SixthSense;
|
||||
import electroblob.wizardry.spell.SlowTime;
|
||||
import electroblob.wizardry.spell.Transience;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.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.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.item.EntityArmorStand;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityDispenser;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.event.FOVUpdateEvent;
|
||||
import net.minecraftforge.client.event.InputUpdateEvent;
|
||||
import net.minecraftforge.client.event.MouseEvent;
|
||||
import net.minecraftforge.client.event.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.minecraftforge.client.event.RenderHandEvent;
|
||||
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.common.gameevent.TickEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Event handler responsible for all client-side only events, mostly rendering.
|
||||
*
|
||||
* General-purpose client-side event handler for things that don't fit anywhere else or groups of related behaviours
|
||||
* that are better kept together.
|
||||
*
|
||||
* @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 pointerTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/pointer.png");
|
||||
private static final ResourceLocation targetPointerTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/target_pointer.png");
|
||||
private WizardryClientEventHandler(){} // No instances!
|
||||
|
||||
// Tick Events
|
||||
// ===============================================================================================================
|
||||
|
||||
@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 && event.phase == TickEvent.Phase.END){
|
||||
|
||||
// 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){ // IntelliJ is wrong, this can be 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())) RenderBlinkEffect.playBlinkEffect();
|
||||
|
||||
Minecraft.getMinecraft().entityRenderer.stopUseShader();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shift-scrolling to change spells
|
||||
// The classes referenced here used to handle their own client tick events, but since they're not client-only
|
||||
// classes, that crashed the dedicated server...
|
||||
@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;
|
||||
|
||||
// Somehow this was throwing a CME, I have no idea why so I'm just going to cheat and copy the list
|
||||
List<TileEntity> tileEntities = new ArrayList<>(world.loadedTileEntityList);
|
||||
|
||||
for(TileEntity tileentity : tileEntities){
|
||||
if(tileentity instanceof TileEntityDispenser){
|
||||
if(DispenserCastingData.get((TileEntityDispenser)tileentity) != null){
|
||||
DispenserCastingData.get((TileEntityDispenser)tileentity).update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpellEmitterData.update(world);
|
||||
PotionSlowTime.cleanUpEntities(world);
|
||||
}
|
||||
}
|
||||
|
||||
// Input and Controls
|
||||
// ===============================================================================================================
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onMouseEvent(MouseEvent event){
|
||||
|
||||
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||
ItemStack wand = player.getHeldItemMainhand();
|
||||
// Prevents the player looking around when paralysed
|
||||
if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.paralysis)
|
||||
&& Minecraft.getMinecraft().inGameHasFocus){
|
||||
event.setCanceled(true);
|
||||
Minecraft.getMinecraft().player.prevRotationYaw = 0;
|
||||
Minecraft.getMinecraft().player.prevRotationPitch = 0;
|
||||
Minecraft.getMinecraft().player.rotationYaw = 0;
|
||||
Minecraft.getMinecraft().player.rotationPitch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
|
||||
&& Wizardry.settings.enableShiftScrolling){
|
||||
if(ItemArtefact.isArtefactActive(event.getEntityPlayer(), WizardryItems.charm_move_speed)
|
||||
&& event.getEntityPlayer().isHandActive()
|
||||
&& event.getEntityPlayer().getActiveItemStack().getItem() instanceof ItemWand){
|
||||
// Normally speed is set to 20% when using items, this makes it 80%
|
||||
event.getMovementInput().moveStrafe *= 4;
|
||||
event.getMovementInput().moveForward *= 4;
|
||||
}
|
||||
}
|
||||
|
||||
event.setCanceled(true);
|
||||
// Rendering
|
||||
// ===============================================================================================================
|
||||
|
||||
if(event.getDwheel() > 0){
|
||||
// Packet building
|
||||
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
|
||||
WizardryPacketHandler.net.sendToServer(msg);
|
||||
@SubscribeEvent
|
||||
public static void onRenderHandEvent(RenderHandEvent event){
|
||||
|
||||
}else if(event.getDwheel() < 0){
|
||||
// Packet building
|
||||
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
|
||||
WizardryPacketHandler.net.sendToServer(msg);
|
||||
// Hide the player's empty hand in first-person when possessing
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,564 +166,57 @@ public final class WizardryClientEventHandler {
|
||||
public static void onFOVUpdateEvent(FOVUpdateEvent event){
|
||||
|
||||
// Bow zoom. Taken directly from AbstractClientPlayer so it works exactly like vanilla.
|
||||
if(event.getEntity().isHandActive() && event.getEntity().getActiveItemStack().getItem() instanceof ItemSpectralBow){
|
||||
if(event.getEntity().isHandActive()){
|
||||
|
||||
int maxUseTicks = event.getEntity().getItemInUseMaxCount();
|
||||
Item item = event.getEntity().getActiveItemStack().getItem();
|
||||
|
||||
float maxUseSeconds = (float)maxUseTicks / 20.0F;
|
||||
if(item instanceof ItemSpectralBow || item instanceof ItemFlamecatcher){
|
||||
|
||||
if(maxUseSeconds > 1.0F){
|
||||
maxUseSeconds = 1.0F;
|
||||
}else{
|
||||
maxUseSeconds = maxUseSeconds * maxUseSeconds;
|
||||
int maxUseTicks = event.getEntity().getItemInUseMaxCount();
|
||||
|
||||
float maxUseSeconds = (float)maxUseTicks / (item instanceof ItemFlamecatcher ? ItemFlamecatcher.DRAW_TIME : 20);
|
||||
|
||||
if(maxUseSeconds > 1.0F){
|
||||
maxUseSeconds = 1.0F;
|
||||
}else{
|
||||
maxUseSeconds = maxUseSeconds * maxUseSeconds;
|
||||
}
|
||||
|
||||
event.setNewfov(event.getFov() * 1.0F - maxUseSeconds * 0.15F);
|
||||
}
|
||||
|
||||
event.setNewfov(event.getFov() * 1.0F - maxUseSeconds * 0.15F);
|
||||
}
|
||||
}
|
||||
|
||||
// Third person
|
||||
@SubscribeEvent
|
||||
public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){
|
||||
renderShieldIfActive(event.getEntityPlayer());
|
||||
renderWingsIfActive(event.getEntityPlayer(), event.getPartialRenderTick());
|
||||
renderShadowWardIfActive(event.getEntityPlayer());
|
||||
}
|
||||
|
||||
// First person
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
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);
|
||||
RenderManager renderManager = event.getRenderer().getRenderManager();
|
||||
|
||||
ItemStack wand = mc.player.getHeldItemMainhand();
|
||||
|
||||
if(!(wand.getItem() instanceof ItemWand)){
|
||||
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){
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
|
||||
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.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);
|
||||
|
||||
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
|
||||
|
||||
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();
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GlStateManager.enableCull();
|
||||
GlStateManager.enableLighting();
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
// Summoned creature selection pointer
|
||||
if(properties != null && properties.selectedMinion != null && properties.selectedMinion.get() == event.getEntity()){
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
|
||||
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.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);
|
||||
|
||||
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
|
||||
|
||||
mc.renderEngine.bindTexture(pointerTexture);
|
||||
|
||||
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();
|
||||
|
||||
GlStateManager.enableCull();
|
||||
GlStateManager.enableLighting();
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
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
|
||||
* (1 + mc.player.getActivePotionEffect(WizardryPotions.sixth_sense).getAmplifier() * Constants.RANGE_INCREASE_PER_LEVEL)){
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
|
||||
GlStateManager.disableCull();
|
||||
GlStateManager.enableBlend();
|
||||
GlStateManager.disableLighting();
|
||||
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.translate(event.getX(), event.getY() + event.getEntity().height * 0.6, 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);
|
||||
|
||||
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
|
||||
|
||||
mc.renderEngine.bindTexture(sixthSenseTexture);
|
||||
|
||||
buffer.pos(-0.6, 0.6, 0).tex(0, 0).endVertex();
|
||||
buffer.pos(0.6, 0.6, 0).tex(1, 0).endVertex();
|
||||
buffer.pos(0.6, -0.6, 0).tex(1, 1).endVertex();
|
||||
buffer.pos(-0.6, -0.6, 0).tex(0, 1).endVertex();
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GlStateManager.enableCull();
|
||||
GlStateManager.disableBlend();
|
||||
GlStateManager.enableLighting();
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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){
|
||||
|
||||
/**
|
||||
* Renders an overlay across the entire screen.
|
||||
* @param resolution The screen resolution
|
||||
* @param texture The overlay texture to display
|
||||
*/
|
||||
public static void renderScreenOverlay(ScaledResolution resolution, ResourceLocation texture){
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
|
||||
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_QUADS, DefaultVertexFormats.POSITION_TEX);
|
||||
|
||||
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.pos(0, resolution.getScaledHeight(), -90).tex(0, 1).endVertex();
|
||||
buffer.pos(resolution.getScaledWidth(), resolution.getScaledHeight(), -90).tex(1, 1).endVertex();
|
||||
buffer.pos(resolution.getScaledWidth(), 0, -90).tex(1, 0).endVertex();
|
||||
buffer.pos(0, 0, -90).tex(0, 0).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,149 @@
|
||||
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.PacketSpellQuickAccess;
|
||||
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;
|
||||
static boolean[] quickAccessKeyPressed = new boolean[ClientProxy.SPELL_QUICK_ACCESS.length];
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
for(int i = 0; i < ClientProxy.SPELL_QUICK_ACCESS.length; i++){
|
||||
if(ClientProxy.SPELL_QUICK_ACCESS[i].isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
|
||||
if(!quickAccessKeyPressed[i]){
|
||||
quickAccessKeyPressed[i] = true;
|
||||
// Packet building
|
||||
selectSpell(wand, i);
|
||||
}
|
||||
}else{
|
||||
quickAccessKeyPressed[i] = 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));
|
||||
}
|
||||
|
||||
private static void selectSpell(ItemStack wand, int index){
|
||||
// GUI switch animation
|
||||
if(((ISpellCastingItem)wand.getItem()).selectSpell(wand, index)){ // Makes sure the spell is set immediately for the client
|
||||
// Packet building (no point sending it unless the client-side spell selection succeeded
|
||||
IMessage msg = new PacketSpellQuickAccess.Message(index);
|
||||
WizardryPacketHandler.net.sendToServer(msg);
|
||||
|
||||
GuiSpellDisplay.playSpellSwitchAnimation(true); // This will do, it's only an animation
|
||||
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package electroblob.wizardry.client.animation;
|
||||
|
||||
import electroblob.wizardry.data.WizardData;
|
||||
import electroblob.wizardry.item.SpellActions;
|
||||
import electroblob.wizardry.spell.Grapple;
|
||||
import electroblob.wizardry.util.GeometryUtils;
|
||||
import electroblob.wizardry.util.InventoryUtils;
|
||||
import net.minecraft.client.model.ModelBiped;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.EnumAction;
|
||||
import net.minecraft.util.EnumHandSide;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
/**
|
||||
* An animation that is associated with a particular {@link net.minecraft.item.EnumAction}. This animation will display
|
||||
* when a player is using an item with that action.
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.3
|
||||
* @see electroblob.wizardry.item.SpellActions
|
||||
*/
|
||||
public abstract class ActionAnimation extends Animation {
|
||||
|
||||
private final EnumAction action;
|
||||
|
||||
public ActionAnimation(EnumAction action){
|
||||
super(action.name());
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDisplay(EntityPlayer player, boolean firstPerson){
|
||||
return !firstPerson && player.getItemInUseMaxCount() > 0 && player.getActiveItemStack().getItemUseAction() == action;
|
||||
}
|
||||
|
||||
/** Called from the client proxy to register all of wizardry's action animations. */
|
||||
public static void register(){
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.POINT){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
// Something (probably some sort of race condition) causes getActiveItemStack to sometimes be wrong
|
||||
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())) return;
|
||||
|
||||
EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand());
|
||||
|
||||
float pitch = (float)Math.toRadians(player.prevRotationPitch + (player.rotationPitch - player.prevRotationPitch) * partialTicks);
|
||||
float yaw = (float)Math.toRadians(player.prevRotationYawHead + (player.rotationYawHead - player.prevRotationYawHead) * partialTicks)
|
||||
- (float)Math.toRadians(player.prevRenderYawOffset + (player.renderYawOffset - player.prevRenderYawOffset) * partialTicks);
|
||||
|
||||
float x = -((float)Math.PI / 2) + pitch + 0.2f;
|
||||
float y = (side == EnumHandSide.RIGHT ? -0.25f : 0.25f) + yaw;
|
||||
|
||||
ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side);
|
||||
arm.setRotation(x, y, 0);
|
||||
}
|
||||
});
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.POINT_UP){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())) return;
|
||||
EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand());
|
||||
ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side);
|
||||
arm.addRotation(-2.2f, side == EnumHandSide.RIGHT ? 0.2f : -0.2f, 0);
|
||||
}
|
||||
});
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.POINT_DOWN){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
float tick = player.getItemInUseMaxCount() + partialTicks; // It's not the "max" use count at all!
|
||||
float y = Math.min(0.4f + tick * 0.05f, 0.7f);
|
||||
EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand());
|
||||
ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side);
|
||||
arm.addRotation(-0.2f, side == EnumHandSide.RIGHT ? y : -y, 0);
|
||||
}
|
||||
});
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.SUMMON){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
float tick = player.getItemInUseMaxCount() + partialTicks; // It's not the "max" use count at all!
|
||||
float x = -Math.min(0.4f + tick * 0.2f, 2f);
|
||||
((ModelRendererExtended)getArmForSide(model, EnumHandSide.RIGHT)).addRotation(x, 1.2f, 0);
|
||||
((ModelRendererExtended)getArmForSide(model, EnumHandSide.LEFT)).addRotation(x, -1.2f, 0);
|
||||
}
|
||||
});
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.GRAPPLE){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
|
||||
WizardData data = WizardData.get(player);
|
||||
RayTraceResult hit = data.getVariable(Grapple.TARGET_KEY);
|
||||
if(hit == null || hit.typeOfHit == RayTraceResult.Type.MISS) return;
|
||||
Vec3d target = hit.hitVec;
|
||||
|
||||
if(hit.entityHit instanceof EntityLivingBase){
|
||||
// If the target is an entity, we need to use the entity's centre rather than the original hit position
|
||||
// because the entity will have moved!
|
||||
target = GeometryUtils.getCentre(hit.entityHit);
|
||||
}
|
||||
|
||||
EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand());
|
||||
|
||||
ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side);
|
||||
|
||||
Vec3d direction = target.subtract(player.getPositionEyes(partialTicks));
|
||||
float yaw = player.prevRenderYawOffset + (player.renderYawOffset - player.prevRenderYawOffset) * partialTicks;
|
||||
|
||||
float pitch = (float)MathHelper.atan2(MathHelper.sqrt(direction.x*direction.x + direction.z*direction.z), direction.y);
|
||||
float x = pitch - (float)Math.PI * 0.9f;
|
||||
float y = -(float)Math.toRadians(yaw) - (float)MathHelper.atan2(direction.x, direction.z);
|
||||
y += (side == EnumHandSide.RIGHT ? -0.25f : 0.25f);
|
||||
if(Math.abs(pitch) < 0.2f) y = arm.rotateAngleY;
|
||||
|
||||
arm.setRotation(x, y, 0);
|
||||
}
|
||||
});
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.IMBUE){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
float tick = player.getItemInUseMaxCount() + partialTicks; // It's not the "max" use count at all!
|
||||
float z = Math.max(1.5f - tick * 0.1f, 0.8f);
|
||||
EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand());
|
||||
ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side);
|
||||
arm.addRotation(-1.2f, side == EnumHandSide.RIGHT ? -0.2f : 0.2f, side == EnumHandSide.RIGHT ? z : -z);
|
||||
if(!player.getHeldItem(InventoryUtils.getHandForSide(player, side.opposite())).isEmpty()){
|
||||
((ModelRendererExtended)getArmForSide(model, side.opposite())).addRotation(-0.8f, side == EnumHandSide.LEFT ? 0.3f : -0.3f, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.THRUST){
|
||||
@Override
|
||||
public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){
|
||||
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())) return;
|
||||
EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand());
|
||||
ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side);
|
||||
float y = side == EnumHandSide.RIGHT ? -0.6f : 0.6f;
|
||||
arm.addRotation(-1.2f, y, 0);
|
||||
if(player.getHeldItem(InventoryUtils.getHandForSide(player, side.opposite())).isEmpty()){
|
||||
ModelRendererExtended otherArm = (ModelRendererExtended)getArmForSide(model, side.opposite());
|
||||
otherArm.setRotation(arm.rotateAngleX - 1.2f, -arm.rotateAngleY - y, otherArm.rotateAngleZ);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package electroblob.wizardry.client.animation;
|
||||
|
||||
import net.minecraft.client.model.ModelBiped;
|
||||
import net.minecraft.client.model.ModelRenderer;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.EnumHandSide;
|
||||
|
||||
/**
|
||||
* Represents a player animation. Use one of the predefined subclasses below for common animation types, or extend this
|
||||
* class directly to define a completely custom animation. Register instances of this class using
|
||||
* {@link PlayerAnimator#registerAnimation(Animation)}.
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.3
|
||||
* @see ActionAnimation
|
||||
*/
|
||||
public abstract class Animation {
|
||||
|
||||
private final String name;
|
||||
|
||||
public Animation(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/** Returns the name of this animation, currently only used for warning messages / debugging. */
|
||||
public String getName(){
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this animation should be displayed for the given player. <i>Note that if two registered
|
||||
* animations have overlapping conditions, both will attempt to display simultaneously, so if they both animate the
|
||||
* same model part, that part may not behave as expected.</i>
|
||||
* @param player The player being animated
|
||||
* @param firstPerson True if the player being animated is the local client player, and they are in first-person
|
||||
* view. In first-person, animations are only useful for animating the player's empty hand.
|
||||
* @return True if the animation should be displayed, false if not.
|
||||
*/
|
||||
public abstract boolean shouldDisplay(EntityPlayer player, boolean firstPerson);
|
||||
|
||||
/**
|
||||
* Sets the rotation of the model parts for this animation. This method is called every time the player is rendered
|
||||
* when {@link Animation#shouldDisplay(EntityPlayer, boolean)} returns true.
|
||||
* @param player The player being animated
|
||||
* @param model The model to animate. All of the standard {@link ModelBiped} parts will already be wrapped and may
|
||||
* be safely cast to {@code ModelRendererExtended} in order to override the rotations set by the model
|
||||
* itself - see {@link ModelRendererExtended ModelRendererExtended} for details.
|
||||
* @param partialTicks The current partial tick time
|
||||
* @param firstPerson True if the player being animated is the local client player, and they are in first-person
|
||||
* view. In first-person, animations are only useful for animating the player's empty hand.
|
||||
*/
|
||||
public abstract void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson);
|
||||
|
||||
/**
|
||||
* Returns whether the boxes that form the second layer of the player's skin (for models that are instances of
|
||||
* {@link net.minecraft.client.model.ModelPlayer ModelPlayer}) should automatically be set to the same angles as
|
||||
* their corresponding first-layer parts.
|
||||
* @param player The player being rendered, for reference
|
||||
* @param firstPerson True if the player being animated is the local client player, and they are in first-person
|
||||
* view. In first-person, animations are only useful for animating the player's empty hand.
|
||||
* @return True to let {@link PlayerAnimator} auto-rotate the second skin layer, false to rotate them manually.
|
||||
*/
|
||||
public boolean autoRotateSecondLayer(EntityPlayer player, boolean firstPerson){
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arm of the given model corresponding to the given side. Function is identical to the method of the
|
||||
* same name in {@link ModelBiped}.
|
||||
* @param model The model to get the arm of
|
||||
* @param side The {@link EnumHandSide} to return the arm for
|
||||
* @return The {@link ModelRenderer} corresponding to the given arm
|
||||
*/
|
||||
public static ModelRenderer getArmForSide(ModelBiped model, EnumHandSide side){
|
||||
return side == EnumHandSide.LEFT ? model.bipedLeftArm : model.bipedRightArm;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package electroblob.wizardry.client.animation;
|
||||
|
||||
import net.minecraft.client.model.ModelBase;
|
||||
import net.minecraft.client.model.ModelBiped;
|
||||
import net.minecraft.client.model.ModelPlayer;
|
||||
import net.minecraft.client.model.ModelRenderer;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link ModelRenderer} which can be swapped into models to allow render pre/post events to
|
||||
* circumvent {@code ModelBase.setRotationAngles(...)} and set model part rotations themselves. Instances of this class
|
||||
* keep a reference to the original {@code ModelRenderer} and delegate all the actual rendering to it.
|
||||
* <p></p>
|
||||
* {@link PlayerAnimator PlayerAnimator} does all the necessary setup with this
|
||||
* class for player animations. It is also possible to set up custom animations for non-player entities with this class,
|
||||
* see {@link ModelRendererExtended#wrap(ModelBiped)} and {@link ModelRendererExtended#wrap(ModelBase, ModelRenderer)}.
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.3
|
||||
*/
|
||||
public class ModelRendererExtended extends ModelRenderer {
|
||||
|
||||
// We probably could have copied all the fields over instead, but this way if anyone else modifies the boxes we
|
||||
// won't overwrite their changes
|
||||
// This way also avoids reflection because we don't need to copy private fields, which is better for performance
|
||||
private ModelRenderer delegate;
|
||||
|
||||
private float actualRotationX;
|
||||
private float actualRotationY;
|
||||
private float actualRotationZ;
|
||||
|
||||
private float extraRotationX;
|
||||
private float extraRotationY;
|
||||
private float extraRotationZ;
|
||||
|
||||
private ModelRendererExtended(ModelBase baseModel, ModelRenderer delegate){
|
||||
super(baseModel, delegate.boxName);
|
||||
this.delegate = delegate;
|
||||
// Copy over any public fields that might get changed
|
||||
this.textureWidth = delegate.textureWidth;
|
||||
this.textureHeight = delegate.textureHeight;
|
||||
this.showModel = delegate.showModel;
|
||||
this.isHidden = delegate.isHidden;
|
||||
this.rotationPointX = delegate.rotationPointX;
|
||||
this.rotationPointY = delegate.rotationPointY;
|
||||
this.rotationPointZ = delegate.rotationPointZ;
|
||||
this.cubeList = delegate.cubeList;
|
||||
this.childModels = delegate.childModels;
|
||||
this.resetRotation();
|
||||
}
|
||||
|
||||
// Static initialisers
|
||||
|
||||
/**
|
||||
* Replaces the given part of the given {@link ModelBase} with a new {@code ModelRendererExtended} that wraps the
|
||||
* original part. This method takes care of modifying the model's box list, but <b>is unable to change any
|
||||
* individual {@link ModelRenderer} fields</b> - this method instead returns the resulting
|
||||
* {@code ModelRendererExtended} to be assigned to the appropriate field.
|
||||
* @param model The model whose part is to be wrapped
|
||||
* @param box The part of the model to wrap, must belong to the above model
|
||||
* @return The resulting wrapped model part, this should be assigned to the appropriate part field in the model. If
|
||||
* the given model part was already wrapped, this method simply returns it unchanged.
|
||||
* @throws IllegalArgumentException if the given box does not belong to the given model
|
||||
*/
|
||||
public static ModelRendererExtended wrap(ModelBase model, ModelRenderer box){
|
||||
if(box instanceof ModelRendererExtended) return (ModelRendererExtended)box; // Ignore already-wrapped parts
|
||||
ModelRendererExtended wrapper = new ModelRendererExtended(model, box);
|
||||
int index = model.boxList.indexOf(box);
|
||||
if(index < 0) throw new IllegalArgumentException(String.format("The given ModelRenderer %s does not belong to the given model %s", box, model));
|
||||
model.boxList.set(index, wrapper); // I doubt the order matters but we may as well put it at the same index
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the given {@link ModelBiped}'s parts with new {@code ModelRendererExtended} versions that wrap the
|
||||
* original parts. If the given model is a {@link ModelPlayer}, the extra boxes for player skin overlays will also
|
||||
* be wrapped.
|
||||
*/
|
||||
public static void wrap(ModelBiped model){
|
||||
// MMmmmmmm wraps
|
||||
model.bipedHead = wrap(model, model.bipedHead);
|
||||
model.bipedBody = wrap(model, model.bipedBody);
|
||||
model.bipedRightArm = wrap(model, model.bipedRightArm);
|
||||
model.bipedLeftArm = wrap(model, model.bipedLeftArm);
|
||||
model.bipedRightLeg = wrap(model, model.bipedRightLeg);
|
||||
model.bipedLeftLeg = wrap(model, model.bipedLeftLeg);
|
||||
model.bipedHeadwear = wrap(model, model.bipedHeadwear);
|
||||
|
||||
if(model instanceof ModelPlayer){
|
||||
((ModelPlayer)model).bipedBodyWear = wrap(model, ((ModelPlayer)model).bipedBodyWear);
|
||||
((ModelPlayer)model).bipedRightArmwear = wrap(model, ((ModelPlayer)model).bipedRightArmwear);
|
||||
((ModelPlayer)model).bipedLeftArmwear = wrap(model, ((ModelPlayer)model).bipedLeftArmwear);
|
||||
((ModelPlayer)model).bipedRightLegwear = wrap(model, ((ModelPlayer)model).bipedRightLegwear);
|
||||
((ModelPlayer)model).bipedLeftLegwear = wrap(model, ((ModelPlayer)model).bipedLeftLegwear);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link ModelBiped} is fully wrapped, i.e. all its parts are instances of
|
||||
* {@code ModelRendererExtended}. If the given model is a {@link ModelPlayer}, this also checks the extra boxes for
|
||||
* player skin overlays.
|
||||
*/
|
||||
public static boolean isWrapped(ModelBiped model){
|
||||
|
||||
boolean flag = true;
|
||||
|
||||
if(model instanceof ModelPlayer){
|
||||
flag = flag && ((ModelPlayer)model).bipedBodyWear instanceof ModelRendererExtended;
|
||||
flag = flag && ((ModelPlayer)model).bipedRightArmwear instanceof ModelRendererExtended;
|
||||
flag = flag && ((ModelPlayer)model).bipedLeftArmwear instanceof ModelRendererExtended;
|
||||
flag = flag && ((ModelPlayer)model).bipedRightLegwear instanceof ModelRendererExtended;
|
||||
flag = flag && ((ModelPlayer)model).bipedLeftLegwear instanceof ModelRendererExtended;
|
||||
}
|
||||
|
||||
return flag
|
||||
&& model.bipedHead instanceof ModelRendererExtended
|
||||
&& model.bipedBody instanceof ModelRendererExtended
|
||||
&& model.bipedRightArm instanceof ModelRendererExtended
|
||||
&& model.bipedLeftArm instanceof ModelRendererExtended
|
||||
&& model.bipedRightLeg instanceof ModelRendererExtended
|
||||
&& model.bipedLeftLeg instanceof ModelRendererExtended
|
||||
&& model.bipedHeadwear instanceof ModelRendererExtended;
|
||||
}
|
||||
|
||||
/** Resets the rotation of this model part to the angle set by the parent model. */
|
||||
public void resetRotation(){
|
||||
this.actualRotationX = Float.NaN;
|
||||
this.actualRotationY = Float.NaN;
|
||||
this.actualRotationZ = Float.NaN;
|
||||
this.extraRotationX = 0;
|
||||
this.extraRotationY = 0;
|
||||
this.extraRotationZ = 0;
|
||||
}
|
||||
|
||||
/** Sets the rotation of this model part, which will overwrite the angle set by the parent model. */
|
||||
public void setRotation(float x, float y, float z){
|
||||
this.actualRotationX = x;
|
||||
this.actualRotationY = y;
|
||||
this.actualRotationZ = z;
|
||||
}
|
||||
|
||||
/** Sets the extra rotation of this model part, which will be added onto the angle set by the parent model. */
|
||||
public void addRotation(float x, float y, float z){
|
||||
this.extraRotationX = x;
|
||||
this.extraRotationY = y;
|
||||
this.extraRotationZ = z;
|
||||
}
|
||||
|
||||
/** Sets the rotation of this model part to the same values as the given box. */
|
||||
public void setRotationTo(ModelRendererExtended box){
|
||||
this.actualRotationX = box.actualRotationX;
|
||||
this.actualRotationY = box.actualRotationY;
|
||||
this.actualRotationZ = box.actualRotationZ;
|
||||
this.extraRotationX = box.extraRotationX;
|
||||
this.extraRotationY = box.extraRotationY;
|
||||
this.extraRotationZ = box.extraRotationZ;
|
||||
}
|
||||
|
||||
// Delegate rendering, but fiddle with the angles first
|
||||
|
||||
@Override
|
||||
public void render(float scale){
|
||||
|
||||
// Need to copy these over each time in case they were changed
|
||||
delegate.showModel = this.showModel;
|
||||
delegate.isHidden = this.isHidden;
|
||||
delegate.rotationPointX = this.rotationPointX;
|
||||
delegate.rotationPointY = this.rotationPointY;
|
||||
delegate.rotationPointZ = this.rotationPointZ;
|
||||
|
||||
if(!Float.isNaN(actualRotationX) && !Float.isNaN(actualRotationY) && !Float.isNaN(actualRotationZ)){
|
||||
delegate.rotateAngleX = actualRotationX;
|
||||
delegate.rotateAngleY = actualRotationY;
|
||||
delegate.rotateAngleZ = actualRotationZ;
|
||||
}else{
|
||||
delegate.rotateAngleX = this.rotateAngleX + extraRotationX;
|
||||
delegate.rotateAngleY = this.rotateAngleY + extraRotationY;
|
||||
delegate.rotateAngleZ = this.rotateAngleZ + extraRotationZ;
|
||||
}
|
||||
|
||||
delegate.render(scale);
|
||||
}
|
||||
|
||||
// Delegate all other methods
|
||||
|
||||
@Override
|
||||
public void addChild(ModelRenderer renderer){
|
||||
delegate.addChild(renderer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderWithRotation(float scale){
|
||||
delegate.renderWithRotation(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRender(float scale){
|
||||
// Exactly the same setup as above, just add item rotation/translation fields and setters
|
||||
// float angle = 1;
|
||||
// float radius = 10;
|
||||
// delegate.rotationPointY -= radius * MathHelper.cos(angle);
|
||||
// delegate.rotationPointZ -= radius * MathHelper.sin(angle);
|
||||
// delegate.rotateAngleX += angle;
|
||||
delegate.postRender(scale);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package electroblob.wizardry.client.animation;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.SpellActions;
|
||||
import electroblob.wizardry.util.InventoryUtils;
|
||||
import electroblob.wizardry.util.JavaUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.AbstractClientPlayer;
|
||||
import net.minecraft.client.model.ModelBiped;
|
||||
import net.minecraft.client.model.ModelPlayer;
|
||||
import net.minecraft.client.model.ModelRenderer;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.entity.Render;
|
||||
import net.minecraft.client.renderer.entity.RenderLivingBase;
|
||||
import net.minecraft.client.renderer.entity.RenderPlayer;
|
||||
import net.minecraft.client.renderer.entity.layers.LayerBipedArmor;
|
||||
import net.minecraft.client.renderer.entity.layers.LayerRenderer;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.EnumAction;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumHandSide;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
import net.minecraftforge.client.event.RenderPlayerEvent;
|
||||
import net.minecraftforge.client.event.RenderSpecificHandEvent;
|
||||
import net.minecraftforge.fml.common.Loader;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles the setup and rendering events for custom player animations, as well as registering of animations. Addons
|
||||
* can also register their own animations here, see {@link PlayerAnimator#registerAnimation(Animation)}.
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.3
|
||||
* @see electroblob.wizardry.item.SpellActions SpellActions
|
||||
*/
|
||||
@Mod.EventBusSubscriber(Side.CLIENT)
|
||||
public class PlayerAnimator {
|
||||
|
||||
// TODO: Attempt to extend this to all bipeds
|
||||
|
||||
private static final List<Animation> animations = new ArrayList<>();
|
||||
|
||||
/** Stores all registered layer renderers for lazy-loading of armour models later. */
|
||||
private static final Map<RenderPlayer, List<LayerRenderer<? extends EntityLivingBase>>> playerLayers = new HashMap<>();
|
||||
/** Stores the main model for each renderer plus all models for registered layer renderers. */
|
||||
private static final Map<RenderPlayer, List<ModelBiped>> playerLayerModels = new HashMap<>();
|
||||
/** Reflected into {@link RenderLivingBase}{@code #layerRenderers}. */
|
||||
private static final Field layerRenderers;
|
||||
|
||||
/** True if wizardry's player animations have been overridden with another mod (currently only MoBends) */
|
||||
private static boolean overridden;
|
||||
|
||||
static {
|
||||
layerRenderers = ObfuscationReflectionHelper.findField(RenderLivingBase.class, "field_177097_h");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new player animation. This method should be called from the {@code init()} phase via a client proxy.
|
||||
* @param animation The animation to register
|
||||
*/
|
||||
// N.B. This can (and will) be called before PlayerAnimator#init() is called
|
||||
public static void registerAnimation(Animation animation){
|
||||
if(animations.contains(animation)){
|
||||
Wizardry.logger.warn("Animation {} is already registered!", animation.getName());
|
||||
}else{
|
||||
animations.add(animation);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if spellcasting animations are enabled, false otherwise. */
|
||||
public static boolean areAnimationsEnabled(){
|
||||
return Wizardry.settings.spellcastingAnimations && !overridden;
|
||||
}
|
||||
|
||||
public static void init(){
|
||||
|
||||
if(Loader.isModLoaded("mobends")) overridden = true;
|
||||
|
||||
if(!areAnimationsEnabled()) return;
|
||||
|
||||
for(RenderPlayer renderer : Minecraft.getMinecraft().getRenderManager().getSkinMap().values()){
|
||||
loadLayersForRenderer(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void loadLayersForRenderer(RenderPlayer renderer){
|
||||
|
||||
List<ModelBiped> models = new ArrayList<>();
|
||||
ModelRendererExtended.wrap(renderer.getMainModel());
|
||||
models.add(renderer.getMainModel());
|
||||
|
||||
try {
|
||||
|
||||
List<LayerRenderer<? extends EntityLivingBase>> layers = (List<LayerRenderer<? extends EntityLivingBase>>)layerRenderers.get(renderer);
|
||||
|
||||
// Save armour layers for lazy-loading later (ignore other layers so iteration is faster)
|
||||
playerLayers.put(renderer, layers.stream().filter(l -> l instanceof LayerBipedArmor).collect(Collectors.toList()));
|
||||
|
||||
for(LayerRenderer<?> layer : layers){
|
||||
|
||||
for(Field field : JavaUtils.getAllFields(layer.getClass())){
|
||||
|
||||
field.setAccessible(true);
|
||||
|
||||
// If your layer model doesn't extend ModelBiped, you DESERVE to be incompatible!
|
||||
// (Just kidding... there's nothing I can do about it anyway)
|
||||
if(field.get(layer) instanceof ModelBiped){
|
||||
ModelBiped model = (ModelBiped)field.get(layer);
|
||||
ModelRendererExtended.wrap(model);
|
||||
models.add(model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}catch(IllegalAccessException e){
|
||||
Wizardry.logger.error("Error during reflective access of render layers: ", e);
|
||||
}
|
||||
|
||||
playerLayerModels.put(renderer, models);
|
||||
}
|
||||
|
||||
private static void updateModels(EntityPlayer player, Render<?> renderer, float partialTicks, boolean firstPerson){
|
||||
|
||||
// Lazy-load models from custom renderers (some mods assign their custom renderers in postInit, so they might
|
||||
// not be picked up from PlayerAnimator#init)
|
||||
if(renderer instanceof RenderPlayer && !playerLayerModels.containsKey(renderer)){
|
||||
loadLayersForRenderer((RenderPlayer)renderer);
|
||||
}
|
||||
|
||||
List<ModelBiped> models = playerLayerModels.get(renderer);
|
||||
|
||||
// If no models were registered for the given renderer, don't do anything
|
||||
if(models != null){
|
||||
|
||||
List<LayerRenderer<? extends EntityLivingBase>> layers = playerLayers.get(renderer);
|
||||
|
||||
// If no layers were registered for the given renderer there's no point trying to lazy-load anything!
|
||||
if(layers != null){
|
||||
|
||||
// Biped armour is special because of Forge's armour item render hook
|
||||
// This needs to be lazy-loaded because we need access to an actual item
|
||||
for(LayerRenderer<? extends EntityLivingBase> layer : layers){
|
||||
if(layer instanceof LayerBipedArmor){
|
||||
for(EntityEquipmentSlot slot : InventoryUtils.ARMOUR_SLOTS){
|
||||
|
||||
ItemStack armour = player.getItemStackFromSlot(slot);
|
||||
// This method could behave dynamically depending on stuff like NBT so there's not really any way to
|
||||
// optimise it further, we *have* to retrieve the model every time (shouldn't be too bad though)
|
||||
ModelBiped model = ForgeHooksClient.getArmorModel(player, armour, slot, ((LayerBipedArmor)layer).getModelFromSlot(slot));
|
||||
|
||||
if(!models.contains(model)){ // Ignore already-wrapped models
|
||||
models.add(model);
|
||||
ModelRendererExtended.wrap(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Animation animation : animations){
|
||||
|
||||
if(animation.shouldDisplay(player, firstPerson)){
|
||||
|
||||
boolean autoRotateSecondLayer = animation.autoRotateSecondLayer(player, firstPerson);
|
||||
|
||||
for(ModelBiped model : models){
|
||||
|
||||
if(!ModelRendererExtended.isWrapped(model)) continue; // Should never happen but somehow it does
|
||||
|
||||
animation.setRotationAngles(player, model, partialTicks, firstPerson);
|
||||
|
||||
if(autoRotateSecondLayer && model instanceof ModelPlayer){
|
||||
alignSecondLayer((ModelPlayer)model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Rotates the second layer boxes of the given (wrapped) player model to match the first layer. */
|
||||
public static void alignSecondLayer(ModelPlayer model){
|
||||
((ModelRendererExtended)model.bipedBodyWear) .setRotationTo((ModelRendererExtended)model.bipedBody);
|
||||
((ModelRendererExtended)model.bipedRightArmwear).setRotationTo((ModelRendererExtended)model.bipedRightArm);
|
||||
((ModelRendererExtended)model.bipedLeftArmwear) .setRotationTo((ModelRendererExtended)model.bipedLeftArm);
|
||||
((ModelRendererExtended)model.bipedRightLegwear).setRotationTo((ModelRendererExtended)model.bipedBody);
|
||||
((ModelRendererExtended)model.bipedLeftLegwear) .setRotationTo((ModelRendererExtended)model.bipedBody);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderHandEvent(RenderSpecificHandEvent event){
|
||||
|
||||
if(!areAnimationsEnabled()) return;
|
||||
|
||||
AbstractClientPlayer player = Minecraft.getMinecraft().player;
|
||||
|
||||
EnumAction action = event.getItemStack().getItemUseAction();
|
||||
|
||||
if(player.isHandActive() && player.getActiveHand() == event.getHand() && SpellActions.getSpellActions().contains(action)){
|
||||
// Minecraft's item renderer helpfully doesn't have a default case for the usage action so it doesn't do any
|
||||
// transformations at all (which is VERY ANNOYING!) - the following lines are from ItemRenderer#transformSideFirstPerson
|
||||
int i = (player.getPrimaryHand() == EnumHandSide.RIGHT) == (event.getHand() == EnumHand.MAIN_HAND) ? 1 : -1;
|
||||
GlStateManager.translate((float)i * 0.56F, -0.52F + event.getEquipProgress() * -0.6F, -0.72F);
|
||||
}
|
||||
|
||||
updateModels(player, Minecraft.getMinecraft().getRenderManager().getEntityRenderObject(player), event.getPartialTicks(), true);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderPlayerPreEvent(RenderPlayerEvent.Pre event){
|
||||
|
||||
if(!areAnimationsEnabled()) return;
|
||||
|
||||
// boolean firstPerson = event.getEntityPlayer() == Minecraft.getMinecraft().player
|
||||
// && Minecraft.getMinecraft().gameSettings.thirdPersonView == 0;
|
||||
|
||||
updateModels(event.getEntityPlayer(), event.getRenderer(), event.getPartialRenderTick(), false);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderPlayerPostEvent(RenderPlayerEvent.Post event){
|
||||
|
||||
if(!areAnimationsEnabled()) return;
|
||||
|
||||
List<ModelBiped> models = playerLayerModels.get(event.getRenderer());
|
||||
|
||||
if(models != null){
|
||||
for(ModelBiped model : models){
|
||||
for(ModelRenderer box : model.boxList){ // For ModelPlayer, this will include the second layer
|
||||
// Some models have extra boxes, they (probably) don't need wrapping but we need this check!
|
||||
if(box instanceof ModelRendererExtended){
|
||||
((ModelRendererExtended)box).resetRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,36 @@
|
||||
package electroblob.wizardry.client.audio;
|
||||
|
||||
import electroblob.wizardry.item.ISpellCastingItem;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
|
||||
/** A special type of moving sound that stops when the player stops charging up a spell, either by releasing the mouse
|
||||
* button or when the charge-up time has elapsed. */
|
||||
public class MovingSoundSpellCharge extends MovingSoundEntity<EntityLivingBase> {
|
||||
|
||||
public MovingSoundSpellCharge(EntityLivingBase entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
|
||||
super(entity, sound, category, volume, pitch, repeat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
|
||||
if(source.isHandActive()){
|
||||
|
||||
ItemStack stack = source.getActiveItemStack();
|
||||
|
||||
if(stack.getItem() instanceof ISpellCastingItem){
|
||||
|
||||
if(source.getItemInUseMaxCount() < ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack).getChargeup()){
|
||||
super.update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.donePlaying = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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<>();
|
||||
|
||||
// The issue we had here was that there is no way of hooking into the client tick loop between the sound manager
|
||||
// update and the world tick (other than via the sounds themselves, but playing other sounds from there results in
|
||||
// CMEs, which is why we made this class in the first place). This meant there was always a gap between consecutive
|
||||
// sounds, which could be heard in-game as a weird audio artefact. The solution is simply to overlap the sounds by
|
||||
// 1 tick, which is fairly straightforward for the end sound, but because the start sound has to finish for us to
|
||||
// know when to play the others, this doubling-up solution is required (bit of a waste of resources but meh).
|
||||
/** A dummy sound with effectively zero volume, played one tick in advance of the actual start sound so the loop
|
||||
* sound can start one tick before the actual start sound ends. */
|
||||
private final ISound dummyStart; // It's the stupidest system ever but it works!
|
||||
|
||||
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, float volume, 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.dummyStart = factory.create(start, category, 0.00001f, false); // Inaudible but not actually 0
|
||||
this.start = factory.create(start, category, volume, false);
|
||||
this.loop = factory.create(loop, category, volume, true);
|
||||
this.end = factory.create(end, category, volume, 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(dummyStart)){
|
||||
Minecraft.getMinecraft().getSoundHandler().playSound(loop);
|
||||
looping = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts playing the end part immediately and marks the loop part to stop next tick. 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().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;
|
||||
}
|
||||
|
||||
/** Stops the start and loop sounds immediately. */
|
||||
protected void stopStartAndLoop(){
|
||||
Minecraft.getMinecraft().getSoundHandler().stopSound(dummyStart);
|
||||
Minecraft.getMinecraft().getSoundHandler().stopSound(start);
|
||||
Minecraft.getMinecraft().getSoundHandler().stopSound(loop);
|
||||
}
|
||||
|
||||
// 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.dummyStart);
|
||||
Minecraft.getMinecraft().getSoundHandler().playDelayedSound(loop.start, 2); // 1 seems to be insufficient
|
||||
}
|
||||
|
||||
@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.stream().filter(s -> s.needsRemoving).forEach(SoundLoop::stopStartAndLoop);
|
||||
activeLoops.removeIf(s -> s.needsRemoving);
|
||||
activeLoops.forEach(SoundLoop::update); // Do this last to allow a 1-tick overlap
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ISoundFactory {
|
||||
ISound create(SoundEvent sound, SoundCategory category, float volume, 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.EntityUtils;
|
||||
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, float volume, ISoundFactory factory, Spell spell){
|
||||
super(start, loop, end, WizardrySounds.SPELLS, volume, 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, volume, (sound, category, v, repeat) -> new MovingSoundEntity<>(source, sound, category, v, pitch, repeat), spell);
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean stillCasting(Spell spell){
|
||||
return EntityUtils.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, sndVolume, (sound, category, v, 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 = v;
|
||||
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,109 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.client.DrawingUtils;
|
||||
import electroblob.wizardry.registry.WizardryTabs;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import net.minecraft.client.gui.GuiTextField;
|
||||
import net.minecraft.client.gui.inventory.GuiContainerCreative;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.creativetab.CreativeTabs;
|
||||
import net.minecraft.util.text.Style;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraftforge.client.event.GuiScreenEvent;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent.Phase;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Locale;
|
||||
|
||||
@EventBusSubscriber(Side.CLIENT)
|
||||
public class CustomCreativeSearchHandler {
|
||||
|
||||
private static final int SEARCH_TOOLTIP_HOVER_TIME = 20;
|
||||
|
||||
private static final Style TOOLTIP_SYNTAX = new Style().setColor(TextFormatting.YELLOW);
|
||||
private static final Style TOOLTIP_BODY = new Style().setColor(TextFormatting.WHITE);
|
||||
|
||||
/** Reflected into {@code GuiContainerCreative#searchField} */
|
||||
private static final Field searchField;
|
||||
|
||||
private static GuiTextField currentSearchField = null;
|
||||
|
||||
private static int searchBarHoverTime;
|
||||
|
||||
static {
|
||||
searchField = ObfuscationReflectionHelper.findField(GuiContainerCreative.class, "field_147062_A");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onInitGuiEvent(GuiScreenEvent.InitGuiEvent.Post event){
|
||||
// Reduces reflection as much as possible - searchField is created on GUI init and never reassigned so we need
|
||||
// not (and should not!) use reflection to retrieve it every time a key is typed
|
||||
if(event.getGui() instanceof GuiContainerCreative){
|
||||
try{
|
||||
currentSearchField = (GuiTextField)searchField.get(event.getGui());
|
||||
}catch(IllegalAccessException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onKeyboardInputEvent(GuiScreenEvent.KeyboardInputEvent.Post event){
|
||||
// Custom creative tab search behaviour
|
||||
if(event.getGui() instanceof GuiContainerCreative){
|
||||
|
||||
GuiContainerCreative gui = (GuiContainerCreative)event.getGui();
|
||||
CreativeTabs tab = CreativeTabs.CREATIVE_TAB_ARRAY[gui.getSelectedTabIndex()];
|
||||
|
||||
if(tab == WizardryTabs.SPELLS){
|
||||
|
||||
GuiContainerCreative.ContainerCreative container = (GuiContainerCreative.ContainerCreative)gui.inventorySlots;
|
||||
container.itemList.clear(); // Required or duplicates will appear!
|
||||
String searchText = currentSearchField.getText().toLowerCase(Locale.ROOT);
|
||||
tab.displayAllRelevantItems(container.itemList);
|
||||
|
||||
if(!searchText.isEmpty()){
|
||||
container.itemList.removeIf(s -> !Spell.byMetadata(s.getMetadata()).matches(searchText));
|
||||
container.scrollTo(0); // Seems to refresh the GUI somehow so it displays correctly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTickEvent(ClientTickEvent event){
|
||||
if(event.phase == Phase.END && searchBarHoverTime > 0 && searchBarHoverTime < SEARCH_TOOLTIP_HOVER_TIME){
|
||||
searchBarHoverTime++;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onDrawScreenPostEvent(GuiScreenEvent.DrawScreenEvent.Post event){
|
||||
|
||||
if(event.getGui() instanceof GuiContainerCreative && currentSearchField != null){
|
||||
|
||||
GuiContainerCreative gui = (GuiContainerCreative)event.getGui();
|
||||
CreativeTabs tab = CreativeTabs.CREATIVE_TAB_ARRAY[gui.getSelectedTabIndex()];
|
||||
|
||||
if(tab == WizardryTabs.SPELLS && DrawingUtils.isPointInRegion(currentSearchField.x, currentSearchField.y, currentSearchField.width, currentSearchField.height, event.getMouseX(), event.getMouseY())){
|
||||
|
||||
if(searchBarHoverTime == 0){
|
||||
searchBarHoverTime++;
|
||||
}else if(searchBarHoverTime == SEARCH_TOOLTIP_HOVER_TIME){
|
||||
event.getGui().drawHoveringText(I18n.format("container." + Wizardry.MODID + ":arcane_workbench.search_tooltip",
|
||||
TOOLTIP_SYNTAX.getFormattingCode(), TOOLTIP_BODY.getFormattingCode()), event.getMouseX(), event.getMouseY());
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
searchBarHoverTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.inventory.ContainerBookshelf;
|
||||
import electroblob.wizardry.tileentity.TileEntityBookshelf;
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.entity.player.InventoryPlayer;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class GuiBookshelf extends GuiContainer {
|
||||
|
||||
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/container/bookshelf.png");
|
||||
/** The player inventory bound to this GUI. */
|
||||
private final InventoryPlayer playerInventory;
|
||||
/** The inventory contained within the corresponding bookshelf. */
|
||||
public IInventory bookshelfInventory;
|
||||
|
||||
public GuiBookshelf(InventoryPlayer playerInv, TileEntityBookshelf bookshelfInv){
|
||||
super(new ContainerBookshelf(playerInv, bookshelfInv));
|
||||
this.playerInventory = playerInv;
|
||||
this.bookshelfInventory = bookshelfInv;
|
||||
this.allowUserInput = false;
|
||||
this.ySize = 148;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks){
|
||||
this.drawDefaultBackground();
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
this.renderHoveredToolTip(mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){
|
||||
String s = this.bookshelfInventory.getDisplayName().getUnformattedText();
|
||||
this.fontRenderer.drawString(s, 8, 6, 4210752);
|
||||
this.fontRenderer.drawString(this.playerInventory.getDisplayName().getUnformattedText(), 8, this.ySize - 96 + 2, 4210752);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY){
|
||||
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
this.mc.getTextureManager().bindTexture(TEXTURE);
|
||||
int i = (this.width - this.xSize) / 2;
|
||||
int j = (this.height - this.ySize) / 2;
|
||||
this.drawTexturedModalRect(i, j, 0, 0, this.xSize, this.ySize);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-5
@@ -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.InventoryUtils;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
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)
|
||||
&& InventoryUtils.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 = InventoryUtils.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.client.DrawingUtils;
|
||||
import electroblob.wizardry.util.ISpellSortable;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class GuiButtonSpellSort extends GuiButton {
|
||||
|
||||
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/container/spell_sort_buttons.png");
|
||||
private static final int TEXTURE_WIDTH = 32;
|
||||
private static final int TEXTURE_HEIGHT = 32;
|
||||
|
||||
public final ISpellSortable.SortType sortType;
|
||||
|
||||
private final ISpellSortable sortable;
|
||||
private final GuiScreen parent;
|
||||
|
||||
public GuiButtonSpellSort(int id, int x, int y, ISpellSortable.SortType sortType, ISpellSortable sortable, GuiScreen parent){
|
||||
super(id, x, y, 10, 10, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.sort_" + sortType.name));
|
||||
this.sortType = sortType;
|
||||
this.sortable = sortable;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
|
||||
|
||||
if(this.visible){
|
||||
|
||||
// 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 = 0;
|
||||
int l = this.sortType.ordinal() * this.height;
|
||||
|
||||
if(sortType == sortable.getSortType()){
|
||||
k += this.width;
|
||||
if(sortable.isSortDescending()) k += this.width;
|
||||
}
|
||||
|
||||
parent.mc.getTextureManager().bindTexture(TEXTURE);
|
||||
DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawButtonForegroundLayer(int mouseX, int mouseY){
|
||||
if(hovered) parent.drawHoveringText(this.displayString, mouseX, mouseY);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
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)
|
||||
public class GuiButtonTurnPage extends GuiButton {
|
||||
|
||||
public static final int WIDTH = 20;
|
||||
public static final int HEIGHT = 12;
|
||||
|
||||
public 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 final ResourceLocation texture;
|
||||
private final int textureWidth, textureHeight;
|
||||
|
||||
public GuiButtonTurnPage(int id, int x, int y, Type type, ResourceLocation texture, int textureWidth, int textureHeight){
|
||||
super(id, x, y, WIDTH, HEIGHT, "");
|
||||
this.type = type;
|
||||
this.texture = texture;
|
||||
this.textureWidth = textureWidth;
|
||||
this.textureHeight = textureHeight;
|
||||
}
|
||||
|
||||
@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, textureWidth, textureHeight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.block.BlockBookshelf;
|
||||
import electroblob.wizardry.client.DrawingUtils;
|
||||
import electroblob.wizardry.client.gui.GuiButtonTurnPage.Type;
|
||||
import electroblob.wizardry.data.SpellGlyphData;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.packet.PacketLectern;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.tileentity.TileEntityLectern;
|
||||
import electroblob.wizardry.util.GeometryUtils;
|
||||
import electroblob.wizardry.util.ISpellSortable;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
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.GuiTextField;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.network.play.client.CPacketCloseWindow;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.text.Style;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GuiLectern extends GuiSpellInfo implements ISpellSortable {
|
||||
|
||||
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/container/lectern.png");
|
||||
/** The distance of the page buttons from the bottom outside corners of the GUI. */
|
||||
private static final int PAGE_BUTTON_INSET_X = 22, PAGE_BUTTON_INSET_Y = 13;
|
||||
/** The distance between adjacent page turn buttons. */
|
||||
private static final int PAGE_BUTTON_SPACING = 20;
|
||||
/** The distance of the sort buttons from the top left corner of the GUI. */
|
||||
private static final int SORT_BUTTON_INSET_X = 96, SORT_BUTTON_INSET_Y = 20;
|
||||
/** The distance between adjacent sort buttons. */
|
||||
private static final int SORT_BUTTON_SPACING = 13;
|
||||
/** The distance of the spell buttons from the top outside corners of the GUI. */
|
||||
private static final int SPELL_BUTTON_INSET_X = 23, SPELL_BUTTON_INSET_Y = 44;
|
||||
/** The distance between adjacent spell buttons (in both x and y). */
|
||||
private static final int SPELL_BUTTON_SPACING = 38;
|
||||
|
||||
private static final int SPELL_ROWS = 3, SPELL_COLUMNS = 3;
|
||||
public static final int SPELL_BUTTON_COUNT = SPELL_ROWS * SPELL_COLUMNS * 2; // x2 because there are 2 pages
|
||||
|
||||
private static final int SEARCH_TOOLTIP_HOVER_TIME = 20;
|
||||
|
||||
private static final Style TOOLTIP_SYNTAX = new Style().setColor(TextFormatting.YELLOW);
|
||||
private static final Style TOOLTIP_BODY = new Style().setColor(TextFormatting.WHITE);
|
||||
|
||||
private final TileEntityLectern lectern;
|
||||
|
||||
private GuiButton nextPageButton;
|
||||
private GuiButton prevPageButton;
|
||||
private GuiButton lastPageButton;
|
||||
private GuiButton firstPageButton;
|
||||
private GuiButton indexButton;
|
||||
private GuiButton locateButton;
|
||||
|
||||
private GuiButton[] sortButtons = new GuiButton[3];
|
||||
private GuiButtonSpell[] spellButtons = new GuiButtonSpell[SPELL_BUTTON_COUNT];
|
||||
|
||||
/** The spell currently being viewed, or null if the index is being viewed. */
|
||||
private Spell currentSpell;
|
||||
/** The available spells in nearby bookshelves; should not contain duplicates. */
|
||||
private List<Spell> availableSpells = new ArrayList<>();
|
||||
/** The spells matching the current search criteria, sorted according to the current sort settings. Will be a subset
|
||||
* of {@link GuiLectern#availableSpells}. */
|
||||
private List<Spell> matchingSpells;
|
||||
|
||||
private ISpellSortable.SortType sortType = ISpellSortable.SortType.TIER;
|
||||
private boolean sortDescending = false;
|
||||
|
||||
private GuiTextField searchField;
|
||||
private boolean searchNeedsClearing;
|
||||
private int searchBarHoverTime;
|
||||
|
||||
private int currentPage = 0;
|
||||
|
||||
public GuiLectern(TileEntityLectern lectern){
|
||||
super(288, 180);
|
||||
this.lectern = lectern;
|
||||
this.currentSpell = lectern.currentSpell;
|
||||
this.setTextureSize(512, 512);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getSpell(){
|
||||
return currentSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(){
|
||||
return TEXTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortType getSortType(){
|
||||
return sortType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSortDescending(){
|
||||
return sortDescending;
|
||||
}
|
||||
|
||||
private int getPageCount(){
|
||||
return MathHelper.ceil((float)matchingSpells.size() / SPELL_BUTTON_COUNT);
|
||||
}
|
||||
|
||||
private Spell getSpellForButton(GuiButtonSpell button){
|
||||
return matchingSpells.get(currentPage * SPELL_BUTTON_COUNT + button.index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initGui(){
|
||||
|
||||
super.initGui();
|
||||
|
||||
final int left = this.width / 2 - this.xSize / 2;
|
||||
final int top = this.height / 2 - this.ySize / 2;
|
||||
|
||||
int buttonID = 0;
|
||||
|
||||
// Page buttons
|
||||
this.buttonList.add(nextPageButton = new GuiButtonTurnPage(buttonID++, left + xSize - PAGE_BUTTON_INSET_X - GuiButtonTurnPage.WIDTH,
|
||||
top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_PAGE, TEXTURE, textureWidth, textureHeight));
|
||||
|
||||
this.buttonList.add(prevPageButton = new GuiButtonTurnPage(buttonID++, left + PAGE_BUTTON_INSET_X,
|
||||
top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE, TEXTURE, textureWidth, textureHeight));
|
||||
|
||||
this.buttonList.add(lastPageButton = new GuiButtonTurnPage(buttonID++, left + xSize - PAGE_BUTTON_INSET_X - GuiButtonTurnPage.WIDTH - PAGE_BUTTON_SPACING,
|
||||
top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_SECTION, TEXTURE, textureWidth, textureHeight));
|
||||
|
||||
this.buttonList.add(firstPageButton = new GuiButtonTurnPage(buttonID++, left + PAGE_BUTTON_INSET_X + PAGE_BUTTON_SPACING,
|
||||
top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION, TEXTURE, textureWidth, textureHeight));
|
||||
|
||||
this.buttonList.add(indexButton = new GuiButtonTurnPage(buttonID++, left + xSize/2 - 23,
|
||||
top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS, TEXTURE, textureWidth, textureHeight));
|
||||
|
||||
this.buttonList.add(locateButton = new GuiButtonLocateBook(buttonID++, left + xSize/2 - 34,
|
||||
top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT));
|
||||
|
||||
// Sort buttons
|
||||
for(SortType sortType : SortType.values()){
|
||||
this.buttonList.add(sortButtons[sortType.ordinal()] = new GuiButtonSpellSort(buttonID++,
|
||||
left + SORT_BUTTON_INSET_X + SORT_BUTTON_SPACING * sortType.ordinal(),
|
||||
top + SORT_BUTTON_INSET_Y, sortType, this, this));
|
||||
}
|
||||
|
||||
// Spell buttons
|
||||
for(int i = 0; i < SPELL_BUTTON_COUNT; i++){
|
||||
|
||||
int row = i % SPELL_COLUMNS;
|
||||
int column = (i / SPELL_COLUMNS) % SPELL_ROWS;
|
||||
|
||||
int x = i < SPELL_BUTTON_COUNT/2 ? SPELL_BUTTON_INSET_X + row * SPELL_BUTTON_SPACING
|
||||
: xSize - SPELL_BUTTON_INSET_X - GuiButtonSpell.WIDTH - (2-row) * SPELL_BUTTON_SPACING;
|
||||
int y = SPELL_BUTTON_INSET_Y + column * SPELL_BUTTON_SPACING;
|
||||
|
||||
this.buttonList.add(spellButtons[i] = new GuiButtonSpell(buttonID++, left + x, top + y, i));
|
||||
}
|
||||
|
||||
this.searchField = new GuiTextField(0, this.fontRenderer, left + 157, top + 21, 106, this.fontRenderer.FONT_HEIGHT);
|
||||
this.searchField.setMaxStringLength(50);
|
||||
this.searchField.setEnableBackgroundDrawing(false);
|
||||
this.searchField.setVisible(true);
|
||||
this.searchField.setTextColor(16777215);
|
||||
this.searchField.setCanLoseFocus(false);
|
||||
this.searchField.setFocused(true);
|
||||
|
||||
refreshAvailableSpells(); // Must be done last
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen(){
|
||||
super.updateScreen();
|
||||
if(searchBarHoverTime > 0 && searchBarHoverTime < SEARCH_TOOLTIP_HOVER_TIME) searchBarHoverTime++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuiClosed(){
|
||||
WizardryPacketHandler.net.sendToServer(new PacketLectern.Message(lectern.getPos(), currentSpell));
|
||||
super.onGuiClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawBackgroundLayer(int left, int top, int mouseX, int mouseY){
|
||||
if(currentSpell == Spells.none){
|
||||
drawIndexPage(left, top);
|
||||
}else{
|
||||
super.drawBackgroundLayer(left, top, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawForegroundLayer(int left, int top, int mouseX, int mouseY){
|
||||
|
||||
if(currentSpell == Spells.none){
|
||||
this.fontRenderer.drawString(I18n.format("gui." + Wizardry.MODID + ":lectern.title"),
|
||||
left + 20, top + SORT_BUTTON_INSET_Y, 0);
|
||||
}else{
|
||||
super.drawForegroundLayer(left, top, mouseX, mouseY);
|
||||
}
|
||||
|
||||
this.buttonList.forEach(b -> b.drawButtonForegroundLayer(mouseX, mouseY));
|
||||
|
||||
// Search tooltip
|
||||
if(DrawingUtils.isPointInRegion(searchField.x, searchField.y, searchField.width, searchField.height, mouseX, mouseY)){
|
||||
if(searchBarHoverTime == 0){
|
||||
searchBarHoverTime++;
|
||||
}else if(searchBarHoverTime == SEARCH_TOOLTIP_HOVER_TIME){
|
||||
drawHoveringText(I18n.format("container." + Wizardry.MODID + ":arcane_workbench.search_tooltip",
|
||||
TOOLTIP_SYNTAX.getFormattingCode(), TOOLTIP_BODY.getFormattingCode()), mouseX, mouseY);
|
||||
}
|
||||
}else{
|
||||
searchBarHoverTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawIndexPage(int left, int top){
|
||||
|
||||
for(int i = 0; i < SPELL_BUTTON_COUNT; i++){
|
||||
|
||||
int index = currentPage * SPELL_BUTTON_COUNT + i;
|
||||
Spell spell = index < matchingSpells.size() ? matchingSpells.get(index) : Spells.none;
|
||||
boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(spell, null);
|
||||
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
|
||||
|
||||
int row = i % SPELL_COLUMNS;
|
||||
int column = (i / SPELL_COLUMNS) % SPELL_ROWS;
|
||||
|
||||
int x = i < SPELL_BUTTON_COUNT/2 ? SPELL_BUTTON_INSET_X + row * SPELL_BUTTON_SPACING
|
||||
: xSize - SPELL_BUTTON_INSET_X - GuiButtonSpell.WIDTH - (2-row) * SPELL_BUTTON_SPACING;
|
||||
int y = SPELL_BUTTON_INSET_Y + column * SPELL_BUTTON_SPACING;
|
||||
|
||||
DrawingUtils.drawTexturedRect(left + x + 1, top + y + 1, 0, 0, 32, 32, 32, 32);
|
||||
}
|
||||
|
||||
mc.renderEngine.bindTexture(getTexture());
|
||||
DrawingUtils.drawTexturedRect(left, top, 0, 256, xSize, ySize, textureWidth, textureHeight);
|
||||
|
||||
this.searchField.drawTextBox();
|
||||
|
||||
GlStateManager.color(1, 1, 1, 1);
|
||||
mc.renderEngine.bindTexture(getTexture());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
searchNeedsClearing = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton button){
|
||||
|
||||
int lastPage = getPageCount() - 1;
|
||||
|
||||
if(button == indexButton){
|
||||
currentSpell = Spells.none;
|
||||
// currentPage = 0;
|
||||
|
||||
}else if(button == locateButton){
|
||||
// Close the GUI
|
||||
this.mc.player.connection.sendPacket(new CPacketCloseWindow(this.mc.player.openContainer.windowId));
|
||||
this.mc.displayGuiScreen(null);
|
||||
|
||||
// Find the location(s) of the current spell's book(s) and highlight them
|
||||
for(IInventory bookshelf : BlockBookshelf.findNearbyBookshelves(lectern.getWorld(), lectern.getPos())){
|
||||
|
||||
for(int i = 0; i < bookshelf.getSizeInventory(); i++){
|
||||
|
||||
ItemStack stack = bookshelf.getStackInSlot(i);
|
||||
|
||||
if(stack.getItem() instanceof ItemSpellBook){
|
||||
|
||||
Spell spell = Spell.byMetadata(stack.getMetadata());
|
||||
|
||||
if(spell == this.currentSpell){
|
||||
|
||||
BlockPos pos = (((TileEntity)bookshelf).getPos());
|
||||
|
||||
for(EnumFacing side : EnumFacing.VALUES){
|
||||
ParticleBuilder.create(ParticleBuilder.Type.BLOCK_HIGHLIGHT).pos(
|
||||
GeometryUtils.getFaceCentre(pos, side)
|
||||
.add(new Vec3d(side.getDirectionVec())
|
||||
.scale(GeometryUtils.ANTI_Z_FIGHTING_OFFSET)))
|
||||
.face(side).clr(0.9f, 0.5f, 0.8f).fade(0.7f, 0, 1).spawn(mc.world);
|
||||
}
|
||||
|
||||
mc.world.playSound(pos, WizardrySounds.BLOCK_LECTERN_LOCATE_SPELL, SoundCategory.BLOCKS, 1, 0.7f, false);
|
||||
|
||||
break; // This bookshelf has the spell, skip to the next bookshelf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else if(button == nextPageButton){
|
||||
if(currentPage < lastPage) currentPage++;
|
||||
|
||||
}else if(button == prevPageButton){
|
||||
if(currentPage > 0) currentPage--;
|
||||
|
||||
}else if(button == lastPageButton){
|
||||
currentPage = lastPage;
|
||||
|
||||
}else if(button == firstPageButton){
|
||||
currentPage = 0;
|
||||
|
||||
}else if(button instanceof GuiButtonSpell){
|
||||
currentSpell = getSpellForButton((GuiButtonSpell)button);
|
||||
|
||||
}else if(button instanceof GuiButtonSpellSort){
|
||||
|
||||
SortType sortType = (((GuiButtonSpellSort)button).sortType);
|
||||
|
||||
if(this.sortType == sortType){
|
||||
this.sortDescending = !this.sortDescending;
|
||||
}else{
|
||||
this.sortType = sortType;
|
||||
this.sortDescending = false;
|
||||
}
|
||||
|
||||
updateMatchingSpells();
|
||||
}
|
||||
|
||||
updateButtonVisiblity();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char typedChar, int keyCode) throws IOException {
|
||||
|
||||
if(this.searchNeedsClearing){
|
||||
this.searchNeedsClearing = false;
|
||||
this.searchField.setText("");
|
||||
this.currentPage = 0;
|
||||
}
|
||||
|
||||
if(this.searchField.textboxKeyTyped(typedChar, keyCode)){
|
||||
this.currentPage = 0;
|
||||
updateMatchingSpells();
|
||||
updateButtonVisiblity();
|
||||
}else{
|
||||
super.keyTyped(typedChar, keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtonVisiblity(){
|
||||
|
||||
if(currentSpell == Spells.none){
|
||||
|
||||
this.searchField.setVisible(true);
|
||||
|
||||
int lastPage = getPageCount() - 1;
|
||||
|
||||
prevPageButton.visible = currentPage > 0;
|
||||
firstPageButton.visible = currentPage > 0;
|
||||
nextPageButton.visible = currentPage < lastPage;
|
||||
lastPageButton.visible = currentPage < lastPage;
|
||||
|
||||
indexButton.visible = false;
|
||||
locateButton.visible = false;
|
||||
|
||||
for(GuiButton button : sortButtons) button.visible = true;
|
||||
|
||||
for(GuiButtonSpell button : spellButtons){
|
||||
button.visible = currentPage * SPELL_BUTTON_COUNT + button.index < matchingSpells.size();
|
||||
}
|
||||
|
||||
}else{
|
||||
this.searchField.setVisible(false);
|
||||
this.buttonList.forEach(b -> b.visible = false); // Hide all buttons...
|
||||
indexButton.visible = true; // ... except the index button and locate button
|
||||
locateButton.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMatchingSpells(){
|
||||
matchingSpells = availableSpells.stream()
|
||||
.filter(s -> s.matches(searchField.getText().toLowerCase(Locale.ROOT)))
|
||||
.sorted(sortDescending ? sortType.comparator.reversed() : sortType.comparator)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// TODO: Config option to always display all spells when in creative
|
||||
/** Called on initialisation and whenever a bookshelf is added or removed, to update the list of spells. */
|
||||
public void refreshAvailableSpells(){
|
||||
|
||||
availableSpells.clear();
|
||||
|
||||
// No need to exclude the lectern TE because it's not an IInventory!
|
||||
for(IInventory bookshelf : BlockBookshelf.findNearbyBookshelves(lectern.getWorld(), lectern.getPos())){
|
||||
|
||||
for(int i=0; i<bookshelf.getSizeInventory(); i++){
|
||||
|
||||
ItemStack stack = bookshelf.getStackInSlot(i);
|
||||
|
||||
if(stack.getItem() instanceof ItemSpellBook){
|
||||
Spell spell = Spell.byMetadata(stack.getMetadata());
|
||||
if(spell != Spells.none && !availableSpells.contains(spell)) availableSpells.add(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!availableSpells.contains(currentSpell)) currentSpell = Spells.none;
|
||||
|
||||
updateMatchingSpells();
|
||||
updateButtonVisiblity();
|
||||
|
||||
}
|
||||
|
||||
private class GuiButtonLocateBook extends GuiButton {
|
||||
|
||||
public GuiButtonLocateBook(int id, int x, int y){
|
||||
super(id, x, y, 12, 12, "");
|
||||
}
|
||||
|
||||
@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, 1, 1, 1);
|
||||
minecraft.getTextureManager().bindTexture(TEXTURE);
|
||||
|
||||
DrawingUtils.drawTexturedRect(this.x, this.y, flag ? width : 0, 184, width, height, textureWidth, textureHeight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class GuiButtonSpell extends GuiButtonInvisible {
|
||||
|
||||
private static final int WIDTH = 34, HEIGHT = 34;
|
||||
|
||||
private final int index;
|
||||
|
||||
public GuiButtonSpell(int id, int x, int y, int index){
|
||||
super(id, x, y, WIDTH, HEIGHT);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@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(visible){
|
||||
super.drawButton(minecraft, mouseX, mouseY, partialTicks);
|
||||
if(hovered){
|
||||
mc.renderEngine.bindTexture(getTexture());
|
||||
DrawingUtils.drawTexturedRect(x, y, 40, 180, width, height, textureWidth, textureHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawButtonForegroundLayer(int mouseX, int mouseY){
|
||||
if(visible && hovered){
|
||||
Spell spell = getSpellForButton(this);
|
||||
if(Wizardry.proxy.shouldDisplayDiscovered(spell, null)){
|
||||
drawHoveringText(Collections.singletonList(spell.getDisplayName()), mouseX, mouseY, fontRenderer);
|
||||
}else{
|
||||
drawHoveringText(Collections.singletonList(SpellGlyphData.getGlyphName(spell, mc.world)),
|
||||
mouseX, mouseY, mc.standardGalacticFontRenderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-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,35 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class GuiSpellBook extends GuiSpellInfo {
|
||||
|
||||
private final ItemSpellBook book;
|
||||
private final Spell spell;
|
||||
|
||||
public GuiSpellBook(ItemStack stack){
|
||||
|
||||
super(288, 180);
|
||||
|
||||
if(!(stack.getItem() instanceof ItemSpellBook)){
|
||||
throw new ClassCastException("Cannot create spell book GUI for item that does not extend ItemSpellBook!");
|
||||
}
|
||||
|
||||
this.book = (ItemSpellBook)stack.getItem();
|
||||
this.spell = (Spell.byMetadata(stack.getItemDamage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getSpell(){
|
||||
return spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(){
|
||||
return book.getGuiTexture(spell);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,698 @@
|
||||
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.SpellModifiers;
|
||||
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 javax.annotation.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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");
|
||||
private static final ResourceLocation CHARGE_METER = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_charge_meter.png");
|
||||
|
||||
/** 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();
|
||||
private static final Random random = new Random();
|
||||
|
||||
/** Width of the charge meter. */
|
||||
private static final int CHARGE_METER_WIDTH = 25;
|
||||
/** Height of the charge meter. */
|
||||
private static final int CHARGE_METER_HEIGHT = 9;
|
||||
|
||||
/** 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. If no such skin exists, returns the default skin as a
|
||||
* fallback. If the fallback skin is missing, prints a warning to the console and returns null. */
|
||||
@Nullable
|
||||
public static Skin getSkin(String key){
|
||||
|
||||
Skin skin = skins.get(key);
|
||||
|
||||
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 null;
|
||||
}
|
||||
}
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
// 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.Post event){
|
||||
|
||||
if(!Wizardry.settings.showSpellHUD && !Wizardry.settings.showChargeMeter) return; // Optimisation
|
||||
|
||||
EntityPlayer player = Minecraft.getMinecraft().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();
|
||||
|
||||
switch(event.getType()){
|
||||
case CROSSHAIRS:
|
||||
renderChargeMeter(player, wand, width, height, event.getPartialTicks());
|
||||
break;
|
||||
case HOTBAR:
|
||||
renderSpellHUD(player, wand, mainHand, width, height, event.getPartialTicks(), false);
|
||||
break;
|
||||
case TEXT:
|
||||
renderSpellHUD(player, wand, mainHand, width, height, event.getPartialTicks(), true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the spell charge meter around the crosshairs.
|
||||
* @param player A reference to the client player
|
||||
* @param wand The wand the HUD is for
|
||||
* @param width The width of the screen
|
||||
* @param height The height of the screen
|
||||
* @param partialTicks The current partial tick time
|
||||
*/
|
||||
private static void renderChargeMeter(EntityPlayer player, ItemStack wand, int width, int height, float partialTicks){
|
||||
|
||||
if(!Wizardry.settings.showChargeMeter) return;
|
||||
if(Minecraft.getMinecraft().gameSettings.showDebugInfo) return; // Don't show charge meter in the debug screen
|
||||
if(Minecraft.getMinecraft().gameSettings.thirdPersonView != 0) return; // Don't show in third person
|
||||
if(wand != player.getActiveItemStack()) return; // Don't show when using the other held item
|
||||
|
||||
if(!(wand.getItem() instanceof ISpellCastingItem)) throw new IllegalArgumentException("The given stack must contain an ISpellCastingItem!");
|
||||
|
||||
Spell spell = ((ISpellCastingItem)wand.getItem()).getCurrentSpell(wand);
|
||||
|
||||
int chargeup = spell.getChargeup();
|
||||
|
||||
if(WizardData.get(player) != null){
|
||||
// Pretty sure this is accessible client-side since it's only assigned from common code
|
||||
chargeup = (int)(chargeup * WizardData.get(player).itemCastingModifiers.get(SpellModifiers.CHARGEUP));
|
||||
}
|
||||
|
||||
if(chargeup <= 0) return;
|
||||
|
||||
// WHY WHY WHY are these methods named so misleadingly?! Sort yourselves out MCP!
|
||||
// (getItemInUseCount returns the max count MINUS the use count, and getItemInUseMaxCount returns the use count)
|
||||
if(player.getItemInUseMaxCount() == 0) return; // Not charging
|
||||
float charge = (player.getItemInUseMaxCount() + partialTicks) / chargeup;
|
||||
if(charge > 1) return; // Done charging
|
||||
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(CHARGE_METER);
|
||||
|
||||
int x1 = width/2 - CHARGE_METER_WIDTH/2;
|
||||
int y = height/2 - CHARGE_METER_HEIGHT/2;
|
||||
int w = (int)(CHARGE_METER_WIDTH/2 * charge);
|
||||
int u = CHARGE_METER_WIDTH - w;
|
||||
|
||||
DrawingUtils.drawTexturedRect(x1, y, 0, 0, w, CHARGE_METER_HEIGHT, 32, 32);
|
||||
DrawingUtils.drawTexturedRect(x1 + u, y, u, 0, w, CHARGE_METER_HEIGHT, 32, 32);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main spell HUD in the corner of the screen.
|
||||
* @param player A reference to the client player
|
||||
* @param wand The wand the HUD is for
|
||||
* @param mainHand True if the wand is in the player's main hand, false if it is in their offhand
|
||||
* @param width The width of the screen
|
||||
* @param height The height of the screen
|
||||
* @param partialTicks The current partial tick time
|
||||
* @param textLayer True to render the text layer, false to render the background (hotbar layer)
|
||||
*/
|
||||
private static void renderSpellHUD(EntityPlayer player, ItemStack wand, boolean mainHand, int width, int height, float partialTicks, boolean textLayer){
|
||||
|
||||
if(!Wizardry.settings.showSpellHUD) return;
|
||||
|
||||
if(!(wand.getItem() instanceof ISpellCastingItem)) throw new IllegalArgumentException("The given stack must contain an ISpellCastingItem!");
|
||||
|
||||
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 = getSkin(Wizardry.settings.spellHUDSkin);
|
||||
|
||||
if(skin == null) return; // Can't draw anything if there is no skin loaded!
|
||||
|
||||
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 = ((ISpellCastingItem)wand.getItem()).getCurrentSpell(wand);
|
||||
int cooldown = ((ISpellCastingItem)wand.getItem()).getCurrentCooldown(wand);
|
||||
int maxCooldown = ((ISpellCastingItem)wand.getItem()).getCurrentMaxCooldown(wand);
|
||||
|
||||
if(textLayer){
|
||||
|
||||
float animationProgress = Math.signum(switchTimer) * ((SPELL_SWITCH_TIME - Math.abs(switchTimer) +
|
||||
partialTicks) / SPELL_SWITCH_TIME);
|
||||
|
||||
String prevSpellName = getFormattedSpellName(((ISpellCastingItem)wand.getItem()).getPreviousSpell(wand), player, 0);
|
||||
String spellName = getFormattedSpellName(spell, player, cooldown);
|
||||
String nextSpellName = getFormattedSpellName(((ISpellCastingItem)wand.getItem()).getNextSpell(wand), player, 0);
|
||||
|
||||
skin.drawText(x, y, flipX, flipY, prevSpellName, spellName, nextSpellName, animationProgress);
|
||||
|
||||
}else{
|
||||
|
||||
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()){
|
||||
// Subtracted partial tick time to make it smoother
|
||||
progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + partialTicks) / maxCooldown;
|
||||
}
|
||||
|
||||
skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative(), player.isPotionActive(WizardryPotions.arcane_jammer));
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
String format = cooldown > 0 ? "\u00A78" : spell.getElement().getFormattingCode();
|
||||
if(!discovered) format = "\u00A79";
|
||||
// Obfuscates the spell name if the player has the arcane jammer effect
|
||||
if(player.isPotionActive(WizardryPotions.arcane_jammer)) format = format + "\u00A7k";
|
||||
|
||||
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(), StandardCharsets.UTF_8));
|
||||
|
||||
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(), StandardCharsets.UTF_8));
|
||||
|
||||
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.
|
||||
* @param jammed True to show the 'glitch' effect (user for arcane jammer), false to draw normally.
|
||||
*/
|
||||
public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode, boolean jammed){
|
||||
|
||||
// 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;
|
||||
|
||||
if(jammed){
|
||||
random.setSeed(Minecraft.getMinecraft().world.getTotalWorldTime() / 2);
|
||||
DrawingUtils.drawGlitchRect(random, x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, false, false);
|
||||
}else{
|
||||
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?
|
||||
if(jammed){
|
||||
DrawingUtils.drawGlitchRect(random, x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY);
|
||||
}else{
|
||||
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
|
||||
|
||||
if(jammed){
|
||||
DrawingUtils.drawGlitchRect(random, x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY);
|
||||
}else{
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.client.DrawingUtils;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.data.SpellGlyphData;
|
||||
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.client.resources.I18n;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
/** Abstract base class for both {@link GuiSpellBook} and {@link GuiLectern}. Centralises code common to both
|
||||
* those classes. */
|
||||
public abstract class GuiSpellInfo extends GuiScreen {
|
||||
|
||||
protected static final String TRANSLATION_KEY_PREFIX = "gui." + Wizardry.MODID + ":spell_book";
|
||||
|
||||
protected final int xSize, ySize;
|
||||
protected int textureWidth = 512;
|
||||
protected int textureHeight = 256;
|
||||
|
||||
public GuiSpellInfo(int xSize, int ySize){
|
||||
super();
|
||||
this.xSize = xSize;
|
||||
this.ySize = ySize;
|
||||
}
|
||||
|
||||
protected void setTextureSize(int width, int height){
|
||||
this.textureWidth = width;
|
||||
this.textureHeight = height;
|
||||
}
|
||||
|
||||
/** Returns the spell to be displayed by this GUI. */
|
||||
public abstract Spell getSpell();
|
||||
|
||||
/** Returns the main texture for the background of this GUI. */
|
||||
public abstract ResourceLocation getTexture();
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks){
|
||||
int left = this.width/2 - xSize/2;
|
||||
int top = this.height/2 - this.ySize/2;
|
||||
this.drawDefaultBackground();
|
||||
this.drawBackgroundLayer(left, top, mouseX, mouseY);
|
||||
super.drawScreen(mouseX, mouseY, partialTicks); // Just draws the buttons
|
||||
this.drawForegroundLayer(left, top, mouseX, mouseY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the background of the spell info GUI. This is called before buttons are drawn.
|
||||
* @param left The x-coordinate of the left-hand edge of the GUI
|
||||
* @param top The y-coordinate of the top edge of the GUI
|
||||
* @param mouseX The current x position of the mouse pointer
|
||||
* @param mouseY The current y position of the mouse pointer
|
||||
*/
|
||||
protected void drawBackgroundLayer(int left, int top, int mouseX, int mouseY){
|
||||
|
||||
boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(getSpell(), null);
|
||||
|
||||
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 ? getSpell().getIcon() : Spells.none.getIcon());
|
||||
DrawingUtils.drawTexturedRect(left + 146, top + 20, 0, 0, 128, 128, 128, 128);
|
||||
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(getTexture());
|
||||
DrawingUtils.drawTexturedRect(left, top, 0, 0, xSize, ySize, textureWidth, textureHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the foreground of the spell info GUI. This is called after buttons are drawn.
|
||||
* @param left The x-coordinate of the left-hand edge of the GUI
|
||||
* @param top The y-coordinate of the top edge of the GUI
|
||||
* @param mouseX The current x position of the mouse pointer
|
||||
* @param mouseY The current y position of the mouse pointer
|
||||
*/
|
||||
protected void drawForegroundLayer(int left, int top, int mouseX, int mouseY){
|
||||
|
||||
boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(getSpell(), null);
|
||||
|
||||
if(discovered){
|
||||
this.fontRenderer.drawString(getSpell().getDisplayName(), left + 17, top + 15, 0);
|
||||
this.fontRenderer.drawString(getSpell().getType().getDisplayName(), left + 17, top + 26, 0x777777);
|
||||
}else{
|
||||
this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(getSpell(), mc.world), left + 17,
|
||||
top + 15, 0);
|
||||
this.mc.standardGalacticFontRenderer.drawString(getSpell().getType().getDisplayName(), left + 17, top + 26,
|
||||
0x777777);
|
||||
}
|
||||
|
||||
// Novice is usually white but this doesn't show up
|
||||
String tier = I18n.format(TRANSLATION_KEY_PREFIX + ".tier", getSpell().getTier() == Tier.NOVICE ?
|
||||
"\u00A77" + getSpell().getTier().getDisplayName() : getSpell().getTier().getDisplayNameWithFormatting());
|
||||
this.fontRenderer.drawString(tier, left + 17, top + 45, 0);
|
||||
|
||||
String element = I18n.format(TRANSLATION_KEY_PREFIX + ".element", getSpell().getElement().getFormattingCode() + getSpell().getElement().getDisplayName());
|
||||
if(!discovered) element = I18n.format(TRANSLATION_KEY_PREFIX + ".element_undiscovered");
|
||||
this.fontRenderer.drawString(element, left + 17, top + 57, 0);
|
||||
|
||||
String manaCost = I18n.format(TRANSLATION_KEY_PREFIX + ".mana_cost", getSpell().getCost());
|
||||
if(getSpell().isContinuous) manaCost = I18n.format(TRANSLATION_KEY_PREFIX + ".mana_cost_continuous", getSpell().getCost());
|
||||
if(!discovered) manaCost = I18n.format(TRANSLATION_KEY_PREFIX + ".mana_cost_undiscovered");
|
||||
this.fontRenderer.drawString(manaCost, left + 17, top + 69, 0);
|
||||
|
||||
if(discovered){
|
||||
this.fontRenderer.drawSplitString(getSpell().getDescription(), left + 17, top + 83, 118, 0);
|
||||
}else{
|
||||
this.mc.standardGalacticFontRenderer.drawSplitString(
|
||||
SpellGlyphData.getGlyphDescription(getSpell(), mc.world), left + 17, top + 83, 118, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initGui(){
|
||||
super.initGui();
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
this.buttonList.clear();
|
||||
this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuiClosed(){
|
||||
super.onGuiClosed();
|
||||
Keyboard.enableRepeatEvents(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesGuiPauseGame(){
|
||||
return Wizardry.settings.booksPauseGame;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package electroblob.wizardry.client.gui;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.client.DrawingUtils;
|
||||
import electroblob.wizardry.data.WizardData;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiMerchant;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.RenderHelper;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.ContainerMerchant;
|
||||
import net.minecraft.inventory.Slot;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.village.MerchantRecipe;
|
||||
import net.minecraft.village.MerchantRecipeList;
|
||||
import net.minecraftforge.client.event.GuiContainerEvent;
|
||||
import net.minecraftforge.client.event.GuiOpenEvent;
|
||||
import net.minecraftforge.client.event.GuiScreenEvent.ActionPerformedEvent;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
import net.minecraftforge.fml.common.eventhandler.EventPriority;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
||||
@EventBusSubscriber(Side.CLIENT)
|
||||
public class WizardTradeTweaksHandler {
|
||||
|
||||
private static final ResourceLocation NEW_SPELL_ICON = new ResourceLocation(Wizardry.MODID, "textures/gui/container/new_spell_indicator.png");
|
||||
|
||||
private static final int ICON_WIDTH = 8;
|
||||
private static final int ICON_HEIGHT = 8;
|
||||
|
||||
private static final int ANIMATION_FRAMES = 4;
|
||||
private static final int ANIMATION_FRAME_TIME = 2; // In ticks
|
||||
private static final int ANIMATION_PERIOD = 40; // In frame durations
|
||||
|
||||
private static int tradeIndex; // Mirrors GuiMerchant#selectedMerchantRecipe (don't want to reflect into it every frame)
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.LOWEST)
|
||||
public static void onGuiOpenEvent(GuiOpenEvent event){
|
||||
if(event.getGui() instanceof GuiMerchant) tradeIndex = 0;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onActionPerformedPostEvent(ActionPerformedEvent.Post event){
|
||||
|
||||
if(event.getGui() instanceof GuiMerchant){
|
||||
|
||||
MerchantRecipeList recipes = ((GuiMerchant)event.getGui()).getMerchant().getRecipes(Minecraft.getMinecraft().player);
|
||||
|
||||
if(recipes == null) return;
|
||||
|
||||
if(event.getButton().id == 1){ // Next
|
||||
tradeIndex = Math.min(tradeIndex + 1, recipes.size());
|
||||
}else if(event.getButton().id == 2){ // Previous
|
||||
tradeIndex = Math.max(tradeIndex - 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brute-force fix for crystals not showing up when a wizard is given a spell book in the trade GUI.
|
||||
@SubscribeEvent
|
||||
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.
|
||||
MerchantRecipeList trades = gui.getMerchant().getRecipes(Minecraft.getMinecraft().player);
|
||||
|
||||
if(trades == null) return;
|
||||
|
||||
// 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 : trades){
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New spell indicator
|
||||
if(gui.inventorySlots instanceof ContainerMerchant){
|
||||
|
||||
if(tradeIndex < trades.size()){ // Seems to happen with certain mods' GUIs
|
||||
|
||||
// Can't use getCurrentRecipe because that only gets updated when the correct items are given
|
||||
MerchantRecipe recipe = trades.get(tradeIndex);
|
||||
|
||||
if(recipe != null && recipe.getItemToSell().getItem() instanceof ItemSpellBook){
|
||||
|
||||
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||
Spell spell = Spell.byMetadata(recipe.getItemToSell().getMetadata());
|
||||
|
||||
if(Wizardry.settings.discoveryMode && !player.isCreative() && Wizardry.proxy.shouldDisplayDiscovered(spell, recipe.getItemToSell())
|
||||
&& WizardData.get(player) != null && !WizardData.get(player).hasSpellBeenDiscovered(spell)){
|
||||
|
||||
int x = gui.inventorySlots.getSlot(2).xPos + 14;
|
||||
int y = gui.inventorySlots.getSlot(2).yPos - 17;
|
||||
|
||||
RenderHelper.enableGUIStandardItemLighting();
|
||||
GlStateManager.color(1, 1, 1);
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(NEW_SPELL_ICON);
|
||||
|
||||
int frame = Math.max(player.ticksExisted / ANIMATION_FRAME_TIME % ANIMATION_PERIOD - (ANIMATION_PERIOD - ANIMATION_FRAMES), 0);
|
||||
DrawingUtils.drawTexturedRect(x, y, 0, frame * ICON_HEIGHT, ICON_WIDTH, ICON_HEIGHT, ICON_WIDTH, ICON_HEIGHT * ANIMATION_FRAMES);
|
||||
|
||||
RenderHelper.disableStandardItemLighting();
|
||||
|
||||
// int mouseX = event.getMouseX() - gui.getGuiLeft();
|
||||
// int mouseY = event.getMouseY() - gui.getGuiTop();
|
||||
//
|
||||
// if(mouseX >= x + 1 && mouseX < x + w + 1 && mouseY >= y - 1 && mouseY < y + h + 1){
|
||||
// GuiUtils.drawHoveringText(Collections.singletonList("You haven't discovered this spell yet"), mouseX,
|
||||
// mouseY, gui.width, gui.height, 150, Minecraft.getMinecraft().fontRenderer);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+6
-10
@@ -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,179 @@
|
||||
package electroblob.wizardry.client.gui.config;
|
||||
|
||||
import com.mojang.realmsclient.gui.ChatFormatting;
|
||||
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"));
|
||||
|
||||
if(Wizardry.settings.hasRedundantKeys()){
|
||||
this.titleLine2 = ChatFormatting.GOLD.toString() + I18n.format("config." + Wizardry.MODID + ".redundant_keys");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<IConfigElement> getConfigEntries(){
|
||||
|
||||
List<IConfigElement> configList = new ArrayList<>(1);
|
||||
|
||||
configList.add(new DummyCategoryElement("gameplayConfig", "config." + Wizardry.MODID + ".category." + Settings.GAMEPLAY_CATEGORY, GameplayCategory.class));
|
||||
configList.add(new DummyCategoryElement("difficultyConfig", "config." + Wizardry.MODID + ".category." + Settings.DIFFICULTY_CATEGORY, DifficultyCategory.class));
|
||||
configList.add(new DummyCategoryElement("worldgenConfig", "config." + Wizardry.MODID + ".category." + Settings.WORLDGEN_CATEGORY, WorldgenCategory.class));
|
||||
configList.add(new DummyCategoryElement("tweaksConfig", "config." + Wizardry.MODID + ".category." + Settings.TWEAKS_CATEGORY, TweaksCategory.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("artefactsConfig", "config." + Wizardry.MODID + ".category." + Settings.ARTEFACTS_CATEGORY, ArtefactsCategory.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; }
|
||||
}
|
||||
|
||||
/** Difficulty category of the config gui. */
|
||||
public static class DifficultyCategory extends CategoryBase {
|
||||
|
||||
public DifficultyCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
|
||||
super(owningScreen, owningEntryList, prop);
|
||||
}
|
||||
|
||||
@Override protected String getCategory() { return Settings.DIFFICULTY_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; }
|
||||
}
|
||||
|
||||
/** Tweaks category of the config gui. */
|
||||
public static class TweaksCategory extends CategoryBase {
|
||||
|
||||
public TweaksCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
|
||||
super(owningScreen, owningEntryList, prop);
|
||||
}
|
||||
|
||||
@Override protected String getCategory() { return Settings.TWEAKS_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; }
|
||||
}
|
||||
|
||||
/** Artefacts category of the config gui. */
|
||||
public static class ArtefactsCategory extends CategoryBase {
|
||||
|
||||
public ArtefactsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
|
||||
super(owningScreen, owningEntryList, prop);
|
||||
}
|
||||
|
||||
@Override protected String getCategory() { return Settings.ARTEFACTS_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,122 @@
|
||||
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);
|
||||
|
||||
if(skin != null){
|
||||
|
||||
float scale = Math.min((previewRight - previewLeft - 2 * previewBorder) / (float)skin.getWidth(),
|
||||
(previewBottom - previewTop - 2 * previewBorder) / (float)skin.getHeight());
|
||||
|
||||
float x = (previewLeft + previewRight) / 2f - (skin.getWidth() * scale) / 2;
|
||||
float y = (previewBottom + previewTop) / 2f + (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, 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.JavaUtils;
|
||||
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 JavaUtils.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), false));
|
||||
|
||||
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,233 @@
|
||||
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 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 javax.annotation.Nullable;
|
||||
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) Wizardry.logger.warn("The recipe {} used in the wizard's handbook does not exist, it will display a blank grid instead", location);
|
||||
else 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.isEmpty() ? null : 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){
|
||||
|
||||
if(recipes.isEmpty()) return;
|
||||
|
||||
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, @Nullable IRecipe recipe){
|
||||
|
||||
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);
|
||||
|
||||
if(recipe != null){
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
ItemStack result = recipe.getRecipeOutput();
|
||||
|
||||
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,229 @@
|
||||
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, boolean spaceless){
|
||||
|
||||
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 (or characters in a spaceless language) at the end of the current line,
|
||||
// treat it as if we started a new line
|
||||
String firstWord = spaceless ? linkWithSuffix.substring(0, 1) : linkWithSuffix.split("\\s")[0];
|
||||
if(font.getStringWidth(firstWord) > 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, boolean spaceless){
|
||||
|
||||
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, spaceless);
|
||||
|
||||
}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, spaceless);
|
||||
}
|
||||
|
||||
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, boolean spaceless){
|
||||
super(id, x, y, font, text, indent, suffix, linesLeft, rightPage, spaceless);
|
||||
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, boolean spaceless){
|
||||
super(id, x, y, font, text, indent, suffix, linesLeft, rightPage, spaceless);
|
||||
this.link = new TextComponentString(text);
|
||||
link.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)).setColor(TextFormatting.DARK_BLUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user