Merge branch '1.20' into 1.20.2

This commit is contained in:
Sollace 2023-12-15 20:38:34 +00:00
commit 0ab31bf3ac
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
666 changed files with 12499 additions and 1781 deletions

View file

@ -72,7 +72,7 @@
- Захват бурь - Захват бурь
Дайте пегасу облако, и у него будет вода на день, дайте пегасу банку, и он поймает облака и устроит дождь на всё лето. Дайте пегасу облако, и у него будет вода на день, дайте пегасу банку, и он поймает облако и устроит дождь на всё лето.
Кажется, так гласит поговорка? Так вот, с помощью банок можно собрать дождь в банку во время грозы. Кажется, так гласит поговорка? Так вот, с помощью банок можно собрать дождь в банку во время грозы.
Это позволит и остановить дождь, и сохранить погоду в банке для последующего использования. Это позволит и остановить дождь, и сохранить погоду в банке для последующего использования.

View file

@ -0,0 +1 @@
{"meta":{"format_version":"4.5","model_format":"java_block","box_uv":false},"name":"block_shell","parent":"","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2.4000000000000004,0,0],"to":[10.399999999999999,0,8],"autouv":0,"color":9,"rotation":[0,45,0],"origin":[12,0,4],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"texture":0},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"1c53fe40-1877-60c2-80b2-135dff0afe56"}],"outliner":["1c53fe40-1877-60c2-80b2-135dff0afe56"],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/Unicopia/src/main/resources/assets/unicopia/textures/item/clam_shell.png","name":"clam_shell.png","folder":"item","namespace":"unicopia","id":"shell","particle":true,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"1a1accdc-ffd7-c20a-9047-a5db72ecf496","relative_path":"../../../src/main/resources/assets/unicopia/textures/item/clam_shell.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAihJREFUOE+l09tL02Ecx/H3+C3XDr9y+21Yk9lhbWyyxYg8BFoXkXRYQlQEi6CCLosg6H+IoIu6CIoIDaKgjPJGwhBUQqVINHYo+0XqXB6mzsPSxm9PYLIQMwifywc+L57vh+erY51Ht848/wQaW9+LzFSaYrsDq8OBGo9y5VTdisxfgdtPW4Sy1UltbZBFQAJsQErT+BJXCQe8hdwq4O6LNrGnJkCp3U5/NFqYsAxILCwwPZ7j4uHqtYGXvb3CqGlYDAasPh8+SUJdZjpa33H+UMXaIzxq+yC8pp/YKyuxAKP9/ej1ejKZDB0D37l+7sSqF6+4eNYTFaEKPzpVJZ/P8zlnIZfLUb/bxafhEVre9uELedA0E0fLnUvZAvCks084t5WhzCRxuVykZBnt8iv8d+oLPWRuqLDRTKOzj9Id2zlZ4dUtATcfNgnbJoHX70dSdrHvwSBjCAzGzeApQj4ik23KoqkTsDiHGs4RzxYRqQksAw3PhVJiR7bacClGJIsFz60EmlFGMphB2gCzEwydMeJ2u3kTizEzJ3H2QOg30PC6RyhbrKRGktgUJwf3eoh3dzNtMuExm5mfnycYDNLV1cXO6mraexKcrvL96eBec7tIqgNYHSVcjRzTPe78KIptNjxuBf3kJD9kmfFEgrJQCPXbLIMDCS7UVa0s8X5zu7h0fH+h1O60EF9jUdyBckaHU4yNDIHIM5VOcy0SXvsj/e9yrXsbfwHRlcQRuWinHQAAAABJRU5ErkJggg=="}]}

View file

@ -0,0 +1 @@
{"meta":{"format_version":"4.5","model_format":"java_block","box_uv":false},"name":"clam_shell_2","parent":"","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[6.4,0,0],"to":[14.399999999999999,0,8],"autouv":0,"color":9,"rotation":[0,45,0],"origin":[12,0,4],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"texture":0},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"1c53fe40-1877-60c2-80b2-135dff0afe56"},{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[1.2117749006091447,0,6.788225099390861],"to":[9.211774900609145,0,14.78822509939086],"autouv":0,"color":9,"rotation":[0,-22.5,0],"origin":[5.211774900609143,0,10.788225099390859],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":90,"texture":0},"down":{"uv":[0,0,0,0],"rotation":270,"texture":null}},"type":"cube","uuid":"f8ee1a59-5c1f-ddd7-783f-bb14a40c36a2"}],"outliner":["1c53fe40-1877-60c2-80b2-135dff0afe56","f8ee1a59-5c1f-ddd7-783f-bb14a40c36a2"],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/Unicopia/src/main/resources/assets/unicopia/textures/item/clam_shell.png","name":"clam_shell.png","folder":"item","namespace":"unicopia","id":"shell","particle":true,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"1a1accdc-ffd7-c20a-9047-a5db72ecf496","relative_path":"../../../src/main/resources/assets/unicopia/textures/item/clam_shell.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAihJREFUOE+l09tL02Ecx/H3+C3XDr9y+21Yk9lhbWyyxYg8BFoXkXRYQlQEi6CCLosg6H+IoIu6CIoIDaKgjPJGwhBUQqVINHYo+0XqXB6mzsPSxm9PYLIQMwifywc+L57vh+erY51Ht848/wQaW9+LzFSaYrsDq8OBGo9y5VTdisxfgdtPW4Sy1UltbZBFQAJsQErT+BJXCQe8hdwq4O6LNrGnJkCp3U5/NFqYsAxILCwwPZ7j4uHqtYGXvb3CqGlYDAasPh8+SUJdZjpa33H+UMXaIzxq+yC8pp/YKyuxAKP9/ej1ejKZDB0D37l+7sSqF6+4eNYTFaEKPzpVJZ/P8zlnIZfLUb/bxafhEVre9uELedA0E0fLnUvZAvCks084t5WhzCRxuVykZBnt8iv8d+oLPWRuqLDRTKOzj9Id2zlZ4dUtATcfNgnbJoHX70dSdrHvwSBjCAzGzeApQj4ik23KoqkTsDiHGs4RzxYRqQksAw3PhVJiR7bacClGJIsFz60EmlFGMphB2gCzEwydMeJ2u3kTizEzJ3H2QOg30PC6RyhbrKRGktgUJwf3eoh3dzNtMuExm5mfnycYDNLV1cXO6mraexKcrvL96eBec7tIqgNYHSVcjRzTPe78KIptNjxuBf3kJD9kmfFEgrJQCPXbLIMDCS7UVa0s8X5zu7h0fH+h1O60EF9jUdyBckaHU4yNDIHIM5VOcy0SXvsj/e9yrXsbfwHRlcQRuWinHQAAAABJRU5ErkJggg=="}]}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"meta":{"format_version":"4.5","model_format":"java_block","box_uv":false},"name":"clam_shell_3","parent":"","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[6.100000000000001,0.4,0.6000000000000014],"to":[14.100000000000005,0.4,8.600000000000001],"autouv":0,"color":9,"rotation":[22.5,0,0],"origin":[10.100000000000005,0.4,3],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":270,"texture":0},"down":{"uv":[0,0,0,0],"rotation":90,"texture":null}},"type":"cube","uuid":"1c53fe40-1877-60c2-80b2-135dff0afe56"},{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0.3640158355865708,0,4.022858234660681],"to":[8.36401583558657,0,12.022858234660681],"autouv":0,"color":9,"rotation":[0,0,22.5],"origin":[4.364015835586571,0,8.022858234660681],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":180,"texture":0},"down":{"uv":[0,0,0,0],"rotation":180,"texture":null}},"type":"cube","uuid":"f8ee1a59-5c1f-ddd7-783f-bb14a40c36a2"},{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7.755052095676865,0,8.084325693581398],"to":[15.755052095676865,0,16.0843256935814],"autouv":0,"color":9,"rotation":[-22.5,0,0],"origin":[11.755052095676865,0,12.0843256935814],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":90,"texture":0},"down":{"uv":[16,0,0,16],"rotation":90,"texture":0}},"type":"cube","uuid":"a13cf19e-6339-91b6-4d9b-0d3500abf734"}],"outliner":["1c53fe40-1877-60c2-80b2-135dff0afe56","f8ee1a59-5c1f-ddd7-783f-bb14a40c36a2","a13cf19e-6339-91b6-4d9b-0d3500abf734"],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/Unicopia/src/main/resources/assets/unicopia/textures/item/clam_shell.png","name":"clam_shell.png","folder":"item","namespace":"unicopia","id":"shell","particle":true,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"1a1accdc-ffd7-c20a-9047-a5db72ecf496","relative_path":"../../../src/main/resources/assets/unicopia/textures/item/clam_shell.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAihJREFUOE+l09tL02Ecx/H3+C3XDr9y+21Yk9lhbWyyxYg8BFoXkXRYQlQEi6CCLosg6H+IoIu6CIoIDaKgjPJGwhBUQqVINHYo+0XqXB6mzsPSxm9PYLIQMwifywc+L57vh+erY51Ht848/wQaW9+LzFSaYrsDq8OBGo9y5VTdisxfgdtPW4Sy1UltbZBFQAJsQErT+BJXCQe8hdwq4O6LNrGnJkCp3U5/NFqYsAxILCwwPZ7j4uHqtYGXvb3CqGlYDAasPh8+SUJdZjpa33H+UMXaIzxq+yC8pp/YKyuxAKP9/ej1ejKZDB0D37l+7sSqF6+4eNYTFaEKPzpVJZ/P8zlnIZfLUb/bxafhEVre9uELedA0E0fLnUvZAvCks084t5WhzCRxuVykZBnt8iv8d+oLPWRuqLDRTKOzj9Id2zlZ4dUtATcfNgnbJoHX70dSdrHvwSBjCAzGzeApQj4ik23KoqkTsDiHGs4RzxYRqQksAw3PhVJiR7bacClGJIsFz60EmlFGMphB2gCzEwydMeJ2u3kTizEzJ3H2QOg30PC6RyhbrKRGktgUJwf3eoh3dzNtMuExm5mfnycYDNLV1cXO6mraexKcrvL96eBec7tIqgNYHSVcjRzTPe78KIptNjxuBf3kJD9kmfFEgrJQCPXbLIMDCS7UVa0s8X5zu7h0fH+h1O60EF9jUdyBckaHU4yNDIHIM5VOcy0SXvsj/e9yrXsbfwHRlcQRuWinHQAAAABJRU5ErkJggg=="}]}

View file

@ -0,0 +1 @@
{"meta":{"format_version":"4.5","model_format":"java_block","box_uv":false},"name":"clam_shell_4","parent":"","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,0.5,0.6000000000000014],"to":[16,0.5,8.600000000000001],"autouv":0,"color":9,"rotation":[22.5,0,0],"origin":[12,0.5,3],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":270,"texture":0},"down":{"uv":[16,0,0,16],"rotation":270,"texture":0}},"type":"cube","uuid":"1c53fe40-1877-60c2-80b2-135dff0afe56"},{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0.3640158355865708,0,7.022858234660681],"to":[8.36401583558657,0,15.022858234660681],"autouv":0,"color":9,"rotation":[22.5,0,0],"origin":[4.364015835586571,0,11.022858234660681],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":270,"texture":0},"down":{"uv":[16,0,0,16],"rotation":270,"texture":0}},"type":"cube","uuid":"f8ee1a59-5c1f-ddd7-783f-bb14a40c36a2"},{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7.755052095676865,0,8.084325693581398],"to":[15.755052095676865,0,16.0843256935814],"autouv":0,"color":9,"rotation":[-22.5,0,0],"origin":[11.755052095676865,0,12.0843256935814],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":90,"texture":0},"down":{"uv":[0,16,16,0],"rotation":270,"texture":0}},"type":"cube","uuid":"a13cf19e-6339-91b6-4d9b-0d3500abf734"},{"name":"cube","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[1.3640158355865708,0,0.022858234660681376],"to":[9.36401583558657,0,8.022858234660681],"autouv":0,"color":9,"rotation":[0,0,22.5],"origin":[5.364015835586571,0,4.022858234660681],"faces":{"north":{"uv":[0,0,8,0],"texture":null},"east":{"uv":[0,0,8,0],"texture":null},"south":{"uv":[0,0,8,0],"texture":null},"west":{"uv":[0,0,8,0],"texture":null},"up":{"uv":[0,0,16,16],"rotation":180,"texture":0},"down":{"uv":[0,16,16,0],"rotation":180,"texture":0}},"type":"cube","uuid":"60843fa4-6abd-3b89-20f7-67fc05e29ba3"}],"outliner":["1c53fe40-1877-60c2-80b2-135dff0afe56","f8ee1a59-5c1f-ddd7-783f-bb14a40c36a2","60843fa4-6abd-3b89-20f7-67fc05e29ba3","a13cf19e-6339-91b6-4d9b-0d3500abf734"],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/Unicopia/src/main/resources/assets/unicopia/textures/item/clam_shell.png","name":"clam_shell.png","folder":"item","namespace":"unicopia","id":"shell","particle":true,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"1a1accdc-ffd7-c20a-9047-a5db72ecf496","relative_path":"../../../src/main/resources/assets/unicopia/textures/item/clam_shell.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAihJREFUOE+l09tL02Ecx/H3+C3XDr9y+21Yk9lhbWyyxYg8BFoXkXRYQlQEi6CCLosg6H+IoIu6CIoIDaKgjPJGwhBUQqVINHYo+0XqXB6mzsPSxm9PYLIQMwifywc+L57vh+erY51Ht848/wQaW9+LzFSaYrsDq8OBGo9y5VTdisxfgdtPW4Sy1UltbZBFQAJsQErT+BJXCQe8hdwq4O6LNrGnJkCp3U5/NFqYsAxILCwwPZ7j4uHqtYGXvb3CqGlYDAasPh8+SUJdZjpa33H+UMXaIzxq+yC8pp/YKyuxAKP9/ej1ejKZDB0D37l+7sSqF6+4eNYTFaEKPzpVJZ/P8zlnIZfLUb/bxafhEVre9uELedA0E0fLnUvZAvCks084t5WhzCRxuVykZBnt8iv8d+oLPWRuqLDRTKOzj9Id2zlZ4dUtATcfNgnbJoHX70dSdrHvwSBjCAzGzeApQj4ik23KoqkTsDiHGs4RzxYRqQksAw3PhVJiR7bacClGJIsFz60EmlFGMphB2gCzEwydMeJ2u3kTizEzJ3H2QOg30PC6RyhbrKRGktgUJwf3eoh3dzNtMuExm5mfnycYDNLV1cXO6mraexKcrvL96eBec7tIqgNYHSVcjRzTPe78KIptNjxuBf3kJD9kmfFEgrJQCPXbLIMDCS7UVa0s8X5zu7h0fH+h1O60EF9jUdyBckaHU4yNDIHIM5VOcy0SXvsj/e9yrXsbfwHRlcQRuWinHQAAAABJRU5ErkJggg=="}]}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,32 @@
// Made with Blockbench 4.8.3
// Exported for Minecraft version 1.17+ for Yarn
// Paste this class into your mod and generate all required imports
public class cloud_chest extends EntityModel<Entity> {
private final ModelPart lid;
private final ModelPart lock_r1;
private final ModelPart bb_main;
public cloud_chest(ModelPart root) {
this.lid = root.getChild("lid");
this.bb_main = root.getChild("bb_main");
}
public static TexturedModelData getTexturedModelData() {
ModelData modelData = new ModelData();
ModelPartData modelPartData = modelData.getRoot();
ModelPartData lid = modelPartData.addChild("lid", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -2.0F, 13.8F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-1.0F, -1.0F, 14.0F, 2.0F, 2.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-7.0F, -5.0F, 0.0F, 14.0F, 5.0F, 14.0F, new Dilation(0.3F)), ModelTransform.of(0.0F, 16.0F, -7.0F, 1.0908F, 0.0F, 0.0F));
ModelPartData lock_r1 = lid.addChild("lock_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-2.0F, -4.0F, -0.5F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(-2.0F, 1.0F, 14.3F, 0.0F, 0.0F, 1.5708F));
ModelPartData bb_main = modelPartData.addChild("bb_main", ModelPartBuilder.create().uv(0, 19).cuboid(-7.0F, -10.0F, -7.0F, 14.0F, 10.0F, 14.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 24.0F, 0.0F));
return TexturedModelData.of(modelData, 64, 64);
}
@Override
public void setAngles(Entity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
}
@Override
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha) {
lid.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
bb_main.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,15 @@
package com.minelittlepony.unicopia;
public enum Availability {
DEFAULT,
COMMANDS,
NONE;
public boolean isSelectable() {
return this == DEFAULT;
}
public boolean isGrantable() {
return this != NONE;
}
}

View file

@ -1,9 +1,11 @@
package com.minelittlepony.unicopia; package com.minelittlepony.unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity; import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.registry.Registries;
import net.minecraft.world.World; import net.minecraft.world.World;
public interface Debug { public interface Debug {
@ -18,6 +20,16 @@ public interface Debug {
} }
TESTS_COMPLETE[0] = true; TESTS_COMPLETE[0] = true;
try {
Registries.ITEM.getEntrySet().forEach(entry -> {
if (SpellTraits.of(entry.getValue()).isEmpty()) {
// Unicopia.LOGGER.warn("No traits registered for item {}", entry.getKey());
}
});
} catch (Throwable t) {
throw new IllegalStateException("Tests failed", t);
}
try { try {
for (var type : BoatEntity.Type.values()) { for (var type : BoatEntity.Type.values()) {
var balloon = UEntities.AIR_BALLOON.create(world); var balloon = UEntities.AIR_BALLOON.create(world);

View file

@ -27,6 +27,7 @@ public interface EquinePredicates {
Predicate<Entity> PLAYER_CHANGELING = IS_PLAYER.and(ofRace(Race.CHANGELING)); Predicate<Entity> PLAYER_CHANGELING = IS_PLAYER.and(ofRace(Race.CHANGELING));
Predicate<Entity> PLAYER_KIRIN = IS_PLAYER.and(ofRace(Race.KIRIN)); Predicate<Entity> PLAYER_KIRIN = IS_PLAYER.and(ofRace(Race.KIRIN));
Predicate<Entity> PLAYER_PEGASUS = IS_PLAYER.and(e -> ((PlayerEntity)e).getAbilities().creativeMode || RACE_INTERACT_WITH_CLOUDS.test(e)); Predicate<Entity> PLAYER_PEGASUS = IS_PLAYER.and(e -> ((PlayerEntity)e).getAbilities().creativeMode || RACE_INTERACT_WITH_CLOUDS.test(e));
Predicate<Entity> PLAYER_SEAPONY = IS_PLAYER.and(raceMatches(Race::isFish));
Predicate<Entity> PLAYER_CAN_USE_EARTH = IS_PLAYER.and(raceMatches(Race::canUseEarth)); Predicate<Entity> PLAYER_CAN_USE_EARTH = IS_PLAYER.and(raceMatches(Race::canUseEarth));
Predicate<Entity> IS_CASTER = e -> !e.isRemoved() && (e instanceof Caster || IS_PLAYER.test(e)); Predicate<Entity> IS_CASTER = e -> !e.isRemoved() && (e instanceof Caster || IS_PLAYER.test(e));

View file

@ -24,37 +24,44 @@ import net.minecraft.util.Identifier;
import net.minecraft.registry.Registry; import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKey;
public record Race (Supplier<Composite> compositeSupplier, boolean canCast, FlightType flightType, boolean canUseEarth, boolean isNocturnal, boolean canHang) implements Affine { public record Race (Supplier<Composite> compositeSupplier, Availability availability, boolean canCast, FlightType flightType, boolean canUseEarth, boolean isNocturnal, boolean canHang) implements Affine {
public static final String DEFAULT_ID = "unicopia:unset"; public static final String DEFAULT_ID = "unicopia:unset";
public static final Registry<Race> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race"), DEFAULT_ID); public static final Registry<Race> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race"), DEFAULT_ID);
public static final Registry<Race> COMMAND_REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race/grantable"), DEFAULT_ID);
public static final RegistryKey<? extends Registry<Race>> REGISTRY_KEY = REGISTRY.getKey(); public static final RegistryKey<? extends Registry<Race>> REGISTRY_KEY = REGISTRY.getKey();
private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("race.unknown", id)); private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("race.unknown", id));
public static Race register(String name, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean canHang) { public static Race register(String name, Availability availability, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean canHang) {
return register(Unicopia.id(name), magic, flight, earth, nocturnal, canHang); return register(Unicopia.id(name), availability, magic, flight, earth, nocturnal, canHang);
} }
public static Race register(Identifier id, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean canHang) { public static Race register(Identifier id, Availability availability, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean canHang) {
return Registry.register(REGISTRY, id, new Race(Suppliers.memoize(() -> new Composite(REGISTRY.get(id), null)), magic, flight, earth, nocturnal, canHang)); Race race = Registry.register(REGISTRY, id, new Race(Suppliers.memoize(() -> new Composite(REGISTRY.get(id), null, null)), availability, magic, flight, earth, nocturnal, canHang));
if (availability.isGrantable()) {
Registry.register(COMMAND_REGISTRY, id, race);
}
return race;
} }
public static RegistryKeyArgumentType<Race> argument() { public static RegistryKeyArgumentType<Race> argument() {
return RegistryKeyArgumentType.registryKey(REGISTRY_KEY); return RegistryKeyArgumentType.registryKey(COMMAND_REGISTRY.getKey());
} }
/** /**
* The default, unset race. * The default, unset race.
* This is used if there are no other races. * This is used if there are no other races.
*/ */
public static final Race UNSET = register("unset", false, FlightType.NONE, false, false, false); public static final Race UNSET = register("unset", Availability.COMMANDS, false, FlightType.NONE, false, false, false);
public static final Race HUMAN = register("human", false, FlightType.NONE, false, false, false); public static final Race HUMAN = register("human", Availability.COMMANDS, false, FlightType.NONE, false, false, false);
public static final Race EARTH = register("earth", false, FlightType.NONE, true, false, false); public static final Race EARTH = register("earth", Availability.DEFAULT, false, FlightType.NONE, true, false, false);
public static final Race UNICORN = register("unicorn", true, FlightType.NONE, false, false, false); public static final Race UNICORN = register("unicorn", Availability.DEFAULT, true, FlightType.NONE, false, false, false);
public static final Race PEGASUS = register("pegasus", false, FlightType.AVIAN, false, false, false); public static final Race PEGASUS = register("pegasus", Availability.DEFAULT, false, FlightType.AVIAN, false, false, false);
public static final Race BAT = register("bat", false, FlightType.AVIAN, false, true, true); public static final Race BAT = register("bat", Availability.DEFAULT, false, FlightType.AVIAN, false, true, true);
public static final Race ALICORN = register("alicorn", true, FlightType.AVIAN, true, false, false); public static final Race ALICORN = register("alicorn", Availability.COMMANDS, true, FlightType.AVIAN, true, false, false);
public static final Race CHANGELING = register("changeling", false, FlightType.INSECTOID, false, false, true); public static final Race CHANGELING = register("changeling", Availability.DEFAULT, false, FlightType.INSECTOID, false, false, true);
public static final Race KIRIN = register("kirin", true, FlightType.NONE, false, false, false); public static final Race KIRIN = register("kirin", Availability.DEFAULT, true, FlightType.NONE, false, false, false);
public static final Race HIPPOGRIFF = register("hippogriff", Availability.DEFAULT, false, FlightType.AVIAN, false, false, false);
public static final Race SEAPONY = register("seapony", Availability.NONE, false, FlightType.NONE, false, false, false);
public static void bootstrap() {} public static void bootstrap() {}
@ -62,8 +69,8 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return compositeSupplier.get(); return compositeSupplier.get();
} }
public Composite composite(@Nullable Race pseudo) { public Composite composite(@Nullable Race pseudo, @Nullable Race potential) {
return pseudo == null ? composite() : new Composite(this, pseudo); return pseudo == null && potential == null ? composite() : new Composite(this, pseudo, potential);
} }
@Override @Override
@ -83,6 +90,10 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return !isHuman(); return !isHuman();
} }
public boolean isFish() {
return this == SEAPONY;
}
public boolean isHuman() { public boolean isHuman() {
return this == UNSET || this == HUMAN; return this == UNSET || this == HUMAN;
} }
@ -91,16 +102,12 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return !isNocturnal(); return !isNocturnal();
} }
public boolean isOp() {
return this == ALICORN;
}
public boolean canFly() { public boolean canFly() {
return !flightType().isGrounded(); return !flightType().isGrounded();
} }
public boolean canInteractWithClouds() { public boolean canInteractWithClouds() {
return canFly() && this != CHANGELING && this != BAT; return canFly() && this != CHANGELING && this != BAT && this != HIPPOGRIFF;
} }
public Identifier getId() { public Identifier getId() {
@ -145,6 +152,10 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return this; return this;
} }
public Race or(Race other) {
return isEquine() ? this : other;
}
@Override @Override
public int hashCode() { public int hashCode() {
return getId().hashCode(); return getId().hashCode();
@ -192,7 +203,7 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return REGISTRY.stream().filter(r -> r.isPermitted(player)).collect(Collectors.toSet()); return REGISTRY.stream().filter(r -> r.isPermitted(player)).collect(Collectors.toSet());
} }
public record Composite (Race physical, @Nullable Race pseudo) { public record Composite (Race physical, @Nullable Race pseudo, @Nullable Race potential) {
public Race collapsed() { public Race collapsed() {
return pseudo == null ? physical : pseudo; return pseudo == null ? physical : pseudo;
} }

View file

@ -0,0 +1,16 @@
package com.minelittlepony.unicopia;
import net.fabricmc.fabric.api.object.builder.v1.world.poi.PointOfInterestHelper;
import net.minecraft.block.AbstractChestBlock;
import net.minecraft.block.Block;
import net.minecraft.registry.Registries;
import net.minecraft.world.poi.PointOfInterestType;
public interface UPOIs {
PointOfInterestType CHESTS = PointOfInterestHelper.register(Unicopia.id("chests"), 1, 64, Registries.BLOCK.getEntrySet().stream()
.map(entry -> entry.getValue())
.filter(b -> b instanceof AbstractChestBlock)
.toArray(Block[]::new));
static void bootstrap() { }
}

View file

@ -14,6 +14,8 @@ public interface USounds {
SoundEvent ENTITY_PLAYER_CORRUPTION = PARTICLE_SOUL_ESCAPE; SoundEvent ENTITY_PLAYER_CORRUPTION = PARTICLE_SOUL_ESCAPE;
SoundEvent ENTITY_PLAYER_BATPONY_SCREECH = register("entity.player.batpony.screech"); SoundEvent ENTITY_PLAYER_BATPONY_SCREECH = register("entity.player.batpony.screech");
SoundEvent ENTITY_PLAYER_HIPPOGRIFF_SCREECH = register("entity.player.hippogriff.screech");
SoundEvent ENTITY_PLAYER_HIPPOGRIFF_PECK = ENTITY_CHICKEN_STEP;
SoundEvent ENTITY_PLAYER_REBOUND = register("entity.player.rebound"); SoundEvent ENTITY_PLAYER_REBOUND = register("entity.player.rebound");
SoundEvent ENTITY_PLAYER_PEGASUS_WINGSFLAP = register("entity.player.pegasus.wingsflap"); SoundEvent ENTITY_PLAYER_PEGASUS_WINGSFLAP = register("entity.player.pegasus.wingsflap");
SoundEvent ENTITY_PLAYER_PEGASUS_FLYING = register("entity.player.pegasus.flying"); SoundEvent ENTITY_PLAYER_PEGASUS_FLYING = register("entity.player.pegasus.flying");
@ -27,6 +29,7 @@ public interface USounds {
SoundEvent ENTITY_PLAYER_UNICORN_TELEPORT = register("entity.player.unicorn.teleport"); SoundEvent ENTITY_PLAYER_UNICORN_TELEPORT = register("entity.player.unicorn.teleport");
SoundEvent ENTITY_PLAYER_KIRIN_RAGE = ENTITY_POLAR_BEAR_WARNING; SoundEvent ENTITY_PLAYER_KIRIN_RAGE = ENTITY_POLAR_BEAR_WARNING;
SoundEvent ENTITY_PLAYER_KIRIN_RAGE_LOOP = register("entity.player.kirin.rage.loop"); SoundEvent ENTITY_PLAYER_KIRIN_RAGE_LOOP = register("entity.player.kirin.rage.loop");
SoundEvent ENTITY_PLAYER_SEAPONY_SONAR = register("entity.player.seapony.sonar", 64);
SoundEvent ENTITY_PLAYER_EARS_RINGING = register("entity.player.ears_ring"); SoundEvent ENTITY_PLAYER_EARS_RINGING = register("entity.player.ears_ring");
SoundEvent ENTITY_PLAYER_HEARTBEAT = register("entity.player.heartbeat"); SoundEvent ENTITY_PLAYER_HEARTBEAT = register("entity.player.heartbeat");
@ -146,6 +149,11 @@ public interface USounds {
return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(id)); return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(id));
} }
static SoundEvent register(String name, float range) {
Identifier id = Unicopia.id(name);
return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(id, range));
}
static void bootstrap() {} static void bootstrap() {}
static final class Vanilla extends SoundEvents {} static final class Vanilla extends SoundEvents {}

View file

@ -1,7 +1,5 @@
package com.minelittlepony.unicopia; package com.minelittlepony.unicopia;
import com.minelittlepony.unicopia.item.toxin.Toxics;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageType; import net.minecraft.entity.damage.DamageType;
@ -27,6 +25,7 @@ public interface UTags {
TagKey<Item> SPOOKED_MOB_DROPS = item("spooked_mob_drops"); TagKey<Item> SPOOKED_MOB_DROPS = item("spooked_mob_drops");
TagKey<Item> IS_DELIVERED_AGGRESSIVELY = item("is_delivered_aggressively"); TagKey<Item> IS_DELIVERED_AGGRESSIVELY = item("is_delivered_aggressively");
TagKey<Item> FLOATS_ON_CLOUDS = item("floats_on_clouds"); TagKey<Item> FLOATS_ON_CLOUDS = item("floats_on_clouds");
TagKey<Item> COOLS_OFF_KIRINS = item("cools_off_kirins");
TagKey<Item> POLEARMS = item("polearms"); TagKey<Item> POLEARMS = item("polearms");
TagKey<Item> HORSE_SHOES = item("horse_shoes"); TagKey<Item> HORSE_SHOES = item("horse_shoes");
@ -78,8 +77,4 @@ public interface UTags {
static TagKey<DimensionType> dimension(String name) { static TagKey<DimensionType> dimension(String name) {
return TagKey.of(RegistryKeys.DIMENSION_TYPE, new Identifier("c", name)); return TagKey.of(RegistryKeys.DIMENSION_TYPE, new Identifier("c", name));
} }
static void bootstrap() {
Toxics.bootstrap();
}
} }

View file

@ -22,6 +22,8 @@ import com.minelittlepony.unicopia.command.Commands;
import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate;
import com.minelittlepony.unicopia.container.SpellbookChapterLoader; import com.minelittlepony.unicopia.container.SpellbookChapterLoader;
import com.minelittlepony.unicopia.container.UScreenHandlers; import com.minelittlepony.unicopia.container.UScreenHandlers;
import com.minelittlepony.unicopia.diet.DietsLoader;
import com.minelittlepony.unicopia.diet.affliction.AfflictionType;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.effect.UPotions; import com.minelittlepony.unicopia.entity.effect.UPotions;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
@ -64,7 +66,6 @@ public class Unicopia implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
Channel.bootstrap(); Channel.bootstrap();
UTags.bootstrap();
UCriteria.bootstrap(); UCriteria.bootstrap();
UEntities.bootstrap(); UEntities.bootstrap();
Commands.bootstrap(); Commands.bootstrap();
@ -83,20 +84,18 @@ public class Unicopia implements ModInitializer {
}); });
NocturnalSleepManager.bootstrap(); NocturnalSleepManager.bootstrap();
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE); registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA));
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(UEnchantments.POISONED_JOKE);
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new TraitLoader());
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(StateMapLoader.INSTANCE);
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(SpellbookChapterLoader.INSTANCE);
UGameEvents.bootstrap(); UGameEvents.bootstrap();
UBlocks.bootstrap(); UBlocks.bootstrap();
UPOIs.bootstrap();
UItems.bootstrap(); UItems.bootstrap();
UPotions.bootstrap(); UPotions.bootstrap();
UParticles.bootstrap(); UParticles.bootstrap();
USounds.bootstrap(); USounds.bootstrap();
Race.bootstrap(); Race.bootstrap();
SpellType.bootstrap(); SpellType.bootstrap();
AfflictionType.bootstrap();
Abilities.bootstrap(); Abilities.bootstrap();
UScreenHandlers.bootstrap(); UScreenHandlers.bootstrap();
UWorldGen.bootstrap(); UWorldGen.bootstrap();
@ -104,6 +103,15 @@ public class Unicopia implements ModInitializer {
UDamageTypes.bootstrap(); UDamageTypes.bootstrap();
} }
private void registerServerDataReloaders(ResourceManagerHelper registry) {
registry.registerReloadListener(TreeTypeLoader.INSTANCE);
registry.registerReloadListener(UEnchantments.POISONED_JOKE);
registry.registerReloadListener(new TraitLoader());
registry.registerReloadListener(StateMapLoader.INSTANCE);
registry.registerReloadListener(SpellbookChapterLoader.INSTANCE);
registry.registerReloadListener(new DietsLoader());
}
public interface SidedAccess { public interface SidedAccess {
Optional<Pony> getPony(); Optional<Pony> getPony();

View file

@ -35,6 +35,9 @@ public class UnicopiaMixinPlugin implements IMixinConfigPlugin {
if (mixinClassName.indexOf("ad_astra") != -1) { if (mixinClassName.indexOf("ad_astra") != -1) {
return FabricLoader.getInstance().isModLoaded("ad_astra"); return FabricLoader.getInstance().isModLoaded("ad_astra");
} }
if (mixinClassName.indexOf("minelp") != -1) {
return FabricLoader.getInstance().isModLoaded("minelp");
}
} }
return true; return true;
} }

View file

@ -24,6 +24,9 @@ public interface Abilities {
.toList(); .toList();
}); });
// all races
Ability<?> CHANGE_FORM = register(new ChangeFormAbility(), "change_form", AbilitySlot.PRIMARY);
// unicorn / alicorn // unicorn / alicorn
Ability<?> CAST = register(new UnicornCastingAbility(), "cast", AbilitySlot.PRIMARY); Ability<?> CAST = register(new UnicornCastingAbility(), "cast", AbilitySlot.PRIMARY);
Ability<?> SHOOT = register(new UnicornProjectileAbility(), "shoot", AbilitySlot.PRIMARY); Ability<?> SHOOT = register(new UnicornProjectileAbility(), "shoot", AbilitySlot.PRIMARY);
@ -42,9 +45,14 @@ public interface Abilities {
Ability<?> RAINBOOM = register(new PegasusRainboomAbility(), "rainboom", AbilitySlot.PRIMARY); Ability<?> RAINBOOM = register(new PegasusRainboomAbility(), "rainboom", AbilitySlot.PRIMARY);
Ability<?> CAPTURE_CLOUD = register(new PegasusCaptureStormAbility(), "capture_cloud", AbilitySlot.SECONDARY); Ability<?> CAPTURE_CLOUD = register(new PegasusCaptureStormAbility(), "capture_cloud", AbilitySlot.SECONDARY);
// pegasus / bat / alicorn / changeling // hippogriff
Ability<?> DASH = register(new FlyingDashAbility(), "dash", AbilitySlot.PRIMARY);
Ability<?> SCREECH = register(new ScreechAbility(), "screech", AbilitySlot.SECONDARY);
Ability<?> PECK = register(new PeckAbility(), "peck", AbilitySlot.SECONDARY);
// pegasus / bat / alicorn / changeling / hippogriff
Ability<?> CARRY = register(new CarryAbility(), "carry", AbilitySlot.PRIMARY); Ability<?> CARRY = register(new CarryAbility(), "carry", AbilitySlot.PRIMARY);
Ability<?> TOGGLE_FLIGHT = register(new PegasusFlightToggleAbility(), "toggle_flight", AbilitySlot.TERTIARY); Ability<?> TOGGLE_FLIGHT = register(new ToggleFlightAbility(), "toggle_flight", AbilitySlot.TERTIARY);
// changeling // changeling
Ability<?> DISGUISE = register(new ChangelingDisguiseAbility(), "disguise", AbilitySlot.SECONDARY); Ability<?> DISGUISE = register(new ChangelingDisguiseAbility(), "disguise", AbilitySlot.SECONDARY);
@ -59,6 +67,9 @@ public interface Abilities {
Ability<?> NIRIK_BLAST = register(new NirikBlastAbility(), "nirik_blast", AbilitySlot.SECONDARY); Ability<?> NIRIK_BLAST = register(new NirikBlastAbility(), "nirik_blast", AbilitySlot.SECONDARY);
Ability<?> KIRIN_CAST = register(new KirinCastingAbility(), "kirin_cast", AbilitySlot.SECONDARY); Ability<?> KIRIN_CAST = register(new KirinCastingAbility(), "kirin_cast", AbilitySlot.SECONDARY);
// seapony
Ability<?> SONAR_PULSE = register(new SeaponySonarPulseAbility(), "sonar_pulse", AbilitySlot.SECONDARY);
static <T extends Ability<?>> T register(T power, String name, AbilitySlot slot) { static <T extends Ability<?>> T register(T power, String name, AbilitySlot slot) {
Identifier id = Unicopia.id(name); Identifier id = Unicopia.id(name);
BY_SLOT.computeIfAbsent(slot, s -> new LinkedHashSet<>()).add(power); BY_SLOT.computeIfAbsent(slot, s -> new LinkedHashSet<>()).add(power);

View file

@ -179,6 +179,10 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
return; return;
} }
if (cooldown > 100 && player.asEntity().isCreative()) {
cooldown = Math.max(10, cooldown - 100);
}
if (cooldown > 0 && cooldown-- > 0) { if (cooldown > 0 && cooldown-- > 0) {
ability.coolDown(player, slot); ability.coolDown(player, slot);

View file

@ -1,55 +1,20 @@
package com.minelittlepony.unicopia.ability; package com.minelittlepony.unicopia.ability;
import java.util.Optional;
import com.minelittlepony.unicopia.AwaitTickQueue; import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Numeric;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.RegistryUtils;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.entity.LivingEntity;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.event.GameEvent;
/** /**
* A magic casting ability for unicorns. * An ability to screeeeeeeeEeEeEeeee!
* (only shields for now)
*/ */
public class BatEeeeAbility implements Ability<Numeric> { public class BatEeeeAbility extends ScreechAbility {
public static final int SELF_SPOOK_PROBABILITY = 20000; public static final int SELF_SPOOK_PROBABILITY = 20000;
public static final int MOB_SPOOK_PROBABILITY = 1000;
@Override
public int getWarmupTime(Pony player) {
return 30;
}
@Override
public int getCooldownTime(Pony player) {
return 5;
}
@Override
public double getCostEstimate(Pony player) {
return 0;
}
@Override
public boolean activateOnEarlyRelease() {
return true;
}
@Override @Override
public boolean canUse(Race race) { public boolean canUse(Race race) {
@ -57,22 +22,7 @@ public class BatEeeeAbility implements Ability<Numeric> {
} }
@Override @Override
public Optional<Numeric> prepare(Pony player) { protected void playSounds(Pony player, Random rng, float strength) {
return player.getAbilities().getActiveStat()
.map(stat -> (int)(stat.getWarmup() * getWarmupTime(player)))
.filter(i -> i >= 0)
.map(Numeric::new);
}
@Override
public Numeric.Serializer<Numeric> getSerializer() {
return Numeric.SERIALIZER;
}
@Override
public boolean apply(Pony player, Numeric data) {
float strength = 1 - MathHelper.clamp(data.type() / (float)getWarmupTime(player), 0, 1);
Random rng = player.asWorld().random;
int count = 1 + rng.nextInt(10) + (int)(strength * 10); int count = 1 + rng.nextInt(10) + (int)(strength * 10);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
@ -81,6 +31,7 @@ public class BatEeeeAbility implements Ability<Numeric> {
1.6F + (rng.nextFloat() - 0.5F) 1.6F + (rng.nextFloat() - 0.5F)
); );
} }
player.asWorld().emitGameEvent(player.asEntity(), GameEvent.ENTITY_ACTION, player.asEntity().getEyePos());
for (int j = 0; j < (int)(strength * 2); j++) { for (int j = 0; j < (int)(strength * 2); j++) {
for (int k = 0; k < count; k++) { for (int k = 0; k < count; k++) {
AwaitTickQueue.scheduleTask(player.asWorld(), w -> { AwaitTickQueue.scheduleTask(player.asWorld(), w -> {
@ -88,59 +39,14 @@ public class BatEeeeAbility implements Ability<Numeric> {
(0.9F + (rng.nextFloat() - 0.5F) / 2F) * strength, (0.9F + (rng.nextFloat() - 0.5F) / 2F) * strength,
1.6F + (rng.nextFloat() - 0.5F) 1.6F + (rng.nextFloat() - 0.5F)
); );
player.asWorld().emitGameEvent(player.asEntity(), GameEvent.ENTITY_ACTION, player.asEntity().getEyePos());
}, rng.nextInt(3)); }, rng.nextInt(3));
} }
} }
if (!player.getPhysics().isFlying()) {
player.setAnimation(Animation.SPREAD_WINGS, Animation.Recipient.ANYONE);
}
Vec3d origin = player.getOriginVector();
if (strength > 0.5F && rng.nextInt(SELF_SPOOK_PROBABILITY) == 0) { if (strength > 0.5F && rng.nextInt(SELF_SPOOK_PROBABILITY) == 0) {
player.asEntity().damage(player.damageOf(UDamageTypes.BAT_SCREECH, player), 0.1F); player.asEntity().damage(player.damageOf(UDamageTypes.BAT_SCREECH, player), 0.1F);
UCriteria.SCREECH_SELF.trigger(player.asEntity()); UCriteria.SCREECH_SELF.trigger(player.asEntity());
} }
int total = player.findAllEntitiesInRange((int)Math.max(1, 8 * strength)).mapToInt(e -> {
if (e instanceof LivingEntity living && !SpellType.SHIELD.isOn(e)) {
boolean isEarthPony = EquinePredicates.PLAYER_EARTH.test(e);
e.damage(player.damageOf(UDamageTypes.BAT_SCREECH, player), isEarthPony ? 0.1F : 0.3F);
if (e.getWorld().random.nextInt(MOB_SPOOK_PROBABILITY) == 0) {
RegistryUtils.pickRandom(e.getWorld(), UTags.SPOOKED_MOB_DROPS).ifPresent(drop -> {
e.dropStack(drop.getDefaultStack());
e.playSound(USounds.Vanilla.ENTITY_ITEM_PICKUP, 1, 0.1F);
UCriteria.SPOOK_MOB.trigger(player.asEntity());
});
}
Vec3d knockVec = origin.subtract(e.getPos()).multiply(strength);
living.takeKnockback((isEarthPony ? 0.3F : 0.5F) * strength, knockVec.getX(), knockVec.getZ());
if (!isEarthPony) {
e.addVelocity(0, 0.1 * strength, 0);
}
Living.updateVelocity(e);
return 1;
}
return 0;
}).sum();
if (total >= 20) {
UCriteria.SCREECH_TWENTY_MOBS.trigger(player.asEntity());
}
return true;
}
@Override
public void warmUp(Pony player, AbilitySlot slot) {
}
@Override
public void coolDown(Pony player, AbilitySlot slot) {
for (int i = 0; i < 20; i++) {
player.addParticle(ParticleTypes.BUBBLE_POP, player.getPhysics().getHeadPosition().toCenterPos(), VecHelper.supply(() -> player.asWorld().getRandom().nextGaussian() - 0.5));
}
} }
} }

View file

@ -38,13 +38,13 @@ public class BatPonyHangAbility implements Ability<Multi> {
@Override @Override
public Optional<Multi> prepare(Pony player) { public Optional<Multi> prepare(Pony player) {
if (player.isHanging()) { if (player.getAcrobatics().isHanging()) {
return Optional.of(new Multi(BlockPos.ZERO, 0)); return Optional.of(new Multi(BlockPos.ZERO, 0));
} }
return TraceHelper.findBlock(player.asEntity(), 5, 1) return TraceHelper.findBlock(player.asEntity(), 5, 1)
.map(BlockPos::down) .map(BlockPos::down)
.filter(player::canHangAt) .filter(player.getAcrobatics()::canHangAt)
.map(pos -> new Multi(pos, 1)); .map(pos -> new Multi(pos, 1));
} }
@ -55,13 +55,13 @@ public class BatPonyHangAbility implements Ability<Multi> {
@Override @Override
public boolean apply(Pony player, Multi data) { public boolean apply(Pony player, Multi data) {
if (data.hitType() == 0 && player.isHanging()) { if (data.hitType() == 0 && player.getAcrobatics().isHanging()) {
player.stopHanging(); player.getAcrobatics().stopHanging();
return true; return true;
} }
if (data.hitType() == 1 && player.canHangAt(data.pos().pos())) { if (data.hitType() == 1 && player.getAcrobatics().canHangAt(data.pos().pos())) {
player.startHanging(data.pos().pos()); player.getAcrobatics().startHanging(data.pos().pos());
} }
return true; return true;

View file

@ -0,0 +1,112 @@
package com.minelittlepony.unicopia.ability;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Identifier;
public class ChangeFormAbility implements Ability<Hit> {
@Override
public int getWarmupTime(Pony player) {
return 10;
}
@Override
public int getCooldownTime(Pony player) {
return 1000;
}
@Override
public boolean canUse(Race.Composite race) {
return race.potential() != null && race.potential() != race.physical();
}
@Override
public boolean canUse(Race race) {
return true;
}
@Override
public Identifier getIcon(Pony player) {
Race potential = player.getCompositeRace().potential();
if (potential == null) {
return Ability.super.getIcon(player);
}
return getId().withPath(p -> "textures/gui/ability/" + p + "_" + potential.getId().getPath() + ".png");
}
@Nullable
@Override
public Optional<Hit> prepare(Pony player) {
return Hit.of(canUse(player.getCompositeRace()));
}
@Override
public Hit.Serializer<Hit> getSerializer() {
return Hit.SERIALIZER;
}
@Override
public double getCostEstimate(Pony player) {
return 5;
}
@Override
public boolean apply(Pony player, Hit data) {
if (prepare(player).isEmpty()) {
return false;
}
List<Pony> targets = getTargets(player).toList();
player.subtractEnergyCost(5 * targets.size());
boolean isTransforming = player.getSuppressedRace().isUnset();
targets.forEach(target -> {
Race supressed = target.getSuppressedRace();
if (target == player || supressed.isUnset() == isTransforming) {
Race actualRace = target.getSpecies();
target.setSpecies(supressed.or(player.getCompositeRace().potential()));
target.setSuppressedRace(isTransforming ? actualRace : Race.UNSET);
}
});
return true;
}
@Override
public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getExertion().addPercent(6);
getTargets(player).forEach(target -> {
if (player.getAbilities().getStat(slot).getWarmup() % 5 == 0) {
player.asWorld().playSound(target.asEntity(), target.getOrigin(), SoundEvents.BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE, SoundCategory.PLAYERS);
}
if (player.asWorld().random.nextInt(5) == 0) {
player.asWorld().playSound(target.asEntity(), target.getOrigin(), USounds.Vanilla.BLOCK_BUBBLE_COLUMN_BUBBLE_POP, SoundCategory.PLAYERS);
}
target.spawnParticles(ParticleTypes.BUBBLE_COLUMN_UP, 15);
target.spawnParticles(ParticleTypes.BUBBLE_POP, 15);
});
}
@Override
public void coolDown(Pony player, AbilitySlot slot) {
}
private Stream<Pony> getTargets(Pony player) {
return Stream.concat(Stream.of(player), FriendshipBraceletItem.getPartyMembers(player, 3));
}
}

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.behaviour.Disguise;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
@ -50,9 +51,13 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
player.getEntityWorld().playSound(null, player.getBlockPos(), USounds.ENTITY_PLAYER_CHANGELING_TRANSFORM, SoundCategory.PLAYERS, 1.4F, 0.4F); player.getEntityWorld().playSound(null, player.getBlockPos(), USounds.ENTITY_PLAYER_CHANGELING_TRANSFORM, SoundCategory.PLAYERS, 1.4F, 0.4F);
iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true) Disguise currentDisguise = iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true)
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE)) .orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE));
.setDisguise(looked);
if (currentDisguise.isOf(looked)) {
looked = null;
}
currentDisguise.setDisguise(looked);
if (!player.isCreative()) { if (!player.isCreative()) {
iplayer.getMagicalReserves().getMana().multiply(0.1F); iplayer.getMagicalReserves().getMana().multiply(0.1F);
@ -65,7 +70,7 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
@Override @Override
public void warmUp(Pony player, AbilitySlot slot) { public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getEnergy().add(20); player.getMagicalReserves().getEnergy().add(2F);
player.spawnParticles(UParticles.CHANGELING_MAGIC, 5); player.spawnParticles(UParticles.CHANGELING_MAGIC, 5);
} }

View file

@ -5,10 +5,12 @@ import java.util.Optional;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.TraceHelper; import com.minelittlepony.unicopia.util.TraceHelper;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.item.BoneMealItem; import net.minecraft.item.BoneMealItem;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
@ -71,8 +73,21 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
ItemStack stack = new ItemStack(Items.BONE_MEAL); ItemStack stack = new ItemStack(Items.BONE_MEAL);
if (BoneMealItem.useOnFertilizable(stack, w, pos) if (state.getBlock() instanceof Growable growable) {
|| BoneMealItem.useOnGround(stack, w, pos, Direction.UP)) { return growable.grow(w, state, pos) ? 1 : 0;
}
if (BoneMealItem.useOnFertilizable(stack, w, pos)) {
if (w.random.nextInt(350) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
w.setBlockState(pos.down(), Blocks.DIRT.getDefaultState());
}
w.setBlockState(pos, UBlocks.PLUNDER_VINE_BUD.getDefaultState());
}
return 1;
}
if (BoneMealItem.useOnGround(stack, w, pos, Direction.UP)) {
return 1; return 1;
} }
@ -92,4 +107,8 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
public void coolDown(Pony player, AbilitySlot slot) { public void coolDown(Pony player, AbilitySlot slot) {
} }
public interface Growable {
boolean grow(World world, BlockState state, BlockPos pos);
}
} }

View file

@ -60,8 +60,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
@Override @Override
public Identifier getIcon(Pony player) { public Identifier getIcon(Pony player) {
Identifier id = Abilities.REGISTRY.getId(this); return getId().withPath(p -> "textures/gui/ability/" + p
return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath()
+ "_" + player.getObservedSpecies().getId().getPath() + "_" + player.getObservedSpecies().getId().getPath()
+ "_" + (getKickDirection(player) > 0 ? "forward" : "backward") + "_" + (getKickDirection(player) > 0 ? "forward" : "backward")
+ ".png"); + ".png");

View file

@ -0,0 +1,76 @@
package com.minelittlepony.unicopia.ability;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.entity.player.Pony;
/**
* Dashing ability for flying creatures.
*/
public class FlyingDashAbility implements Ability<Hit> {
@Override
public int getWarmupTime(Pony player) {
return 19;
}
@Override
public int getCooldownTime(Pony player) {
return 30;
}
@Override
public boolean canUse(Race race) {
return race == Race.HIPPOGRIFF;
}
@Nullable
@Override
public Optional<Hit> prepare(Pony player) {
return Hit.of(player.getPhysics().isFlying());
}
@Override
public Hit.Serializer<Hit> getSerializer() {
return Hit.SERIALIZER;
}
@Override
public double getCostEstimate(Pony player) {
return 0;
}
@Override
public boolean onQuickAction(Pony player, ActivationType type, Optional<Hit> data) {
if (type == ActivationType.TAP && !player.getMotion().isRainbooming() && player.getPhysics().isFlying() && player.getMagicalReserves().getMana().get() > 40) {
player.getPhysics().dashForward((float)player.asWorld().random.nextTriangular(2.5F, 0.3F));
return true;
}
return false;
}
@Override
public boolean apply(Pony player, Hit data) {
player.getPhysics().dashForward((float)player.asWorld().random.nextTriangular(2.5F, 0.3F));
player.subtractEnergyCost(2);
return true;
}
@Override
public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getExertion().addPercent(6);
}
@Override
public void coolDown(Pony player, AbilitySlot slot) {
float velocityScale = player.getAbilities().getStat(slot).getCooldown();
float multiplier = 1 + (0.02F * velocityScale);
player.asEntity().getVelocity().multiply(multiplier, 1, multiplier);
}
}

View file

@ -0,0 +1,152 @@
package com.minelittlepony.unicopia.ability;
import java.util.Optional;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Numeric;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.TraceHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
import net.minecraft.world.WorldEvents;
import net.minecraft.world.event.GameEvent;
/**
* Hippogriff ability to use their beak as a weapon
*/
public class PeckAbility implements Ability<Hit> {
@Override
public int getWarmupTime(Pony player) {
return 1;
}
@Override
public int getCooldownTime(Pony player) {
return 10;
}
@Override
public double getCostEstimate(Pony player) {
return 0;
}
@Override
public boolean activateOnEarlyRelease() {
return true;
}
@Override
public boolean canUse(Race race) {
return race == Race.HIPPOGRIFF;
}
@Override
public Optional<Hit> prepare(Pony player) {
return Hit.INSTANCE;
}
@Override
public Numeric.Serializer<Hit> getSerializer() {
return Hit.SERIALIZER;
}
protected LivingEntity findTarget(PlayerEntity player, World w) {
return TraceHelper.<LivingEntity>findEntity(player, 5, 1, hit -> {
return EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.test(hit) && !player.isConnectedThroughVehicle(hit);
}).orElse(null);
}
@Override
public boolean apply(Pony player, Hit hit) {
LivingEntity target = findTarget(player.asEntity(), player.asWorld());
playSounds(player, player.asWorld().getRandom(), 1);
if (target != null) {
spookMob(player, target, 1);
} else {
BlockPos pos = TraceHelper.findBlock(player.asEntity(), 5, 1).orElse(BlockPos.ORIGIN);
if (pos != BlockPos.ORIGIN) {
BlockState state = player.asWorld().getBlockState(pos);
if (state.isReplaceable()) {
player.asWorld().breakBlock(pos, true);
} else if (state.isIn(BlockTags.DIRT) || player.asWorld().random.nextInt(40000) == 0) {
player.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, pos, Block.getRawIdFromState(state));
pos = pos.up();
World world = player.asWorld();
if (world instanceof ServerWorld sw) {
for (ItemStack stack : Block.getDroppedStacks(state, sw, pos, null)) {
if (Block.getBlockFromItem(stack.getItem()) == Blocks.AIR) {
Block.dropStack(world, pos, stack);
}
}
state.onStacksDropped(sw, pos, ItemStack.EMPTY, true);
if (world.random.nextInt(20) == 0) {
world.breakBlock(pos.down(), false);
player.asEntity().sendMessage(Text.translatable("ability.unicopia.peck.block.fled"), true);
}
}
} else {
player.asEntity().sendMessage(Text.translatable("ability.unicopia.peck.block.unfased"), true);
}
}
}
return true;
}
protected void playSounds(Pony player, Random rng, float strength) {
player.getMagicalReserves().getExertion().addPercent(100);
player.getMagicalReserves().getEnergy().addPercent(10);
player.playSound(USounds.Vanilla.ENTITY_CHICKEN_HURT,
1,
0.9F + (rng.nextFloat() - 0.5F)
);
player.asWorld().emitGameEvent(player.asEntity(), GameEvent.STEP, player.asEntity().getEyePos());
}
protected void spookMob(Pony player, LivingEntity living, float strength) {
boolean isEarthPony = EquinePredicates.PLAYER_EARTH.test(living);
boolean isBracing = isEarthPony && player.asEntity().isSneaking();
if (!isBracing) {
living.damage(player.damageOf(UDamageTypes.BAT_SCREECH, player), isEarthPony ? 0.1F : 0.3F);
}
Vec3d knockVec = player.getOriginVector().subtract(living.getPos()).multiply(strength);
living.takeKnockback((isBracing ? 0.2F : isEarthPony ? 0.3F : 0.5F) * strength, knockVec.getX(), knockVec.getZ());
if (!isEarthPony) {
living.addVelocity(0, 0.1 * strength, 0);
}
Living.updateVelocity(living);
}
@Override
public void warmUp(Pony player, AbilitySlot slot) {
}
@Override
public void coolDown(Pony player, AbilitySlot slot) {
}
}

View file

@ -0,0 +1,141 @@
package com.minelittlepony.unicopia.ability;
import java.util.Optional;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Numeric;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.RegistryUtils;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.entity.LivingEntity;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.event.GameEvent;
/**
* An ability to scream very loud
*/
public class ScreechAbility implements Ability<Numeric> {
public static final int MOB_SPOOK_PROBABILITY = 1000;
@Override
public int getWarmupTime(Pony player) {
return 30;
}
@Override
public int getCooldownTime(Pony player) {
return 5;
}
@Override
public double getCostEstimate(Pony player) {
return 0;
}
@Override
public boolean activateOnEarlyRelease() {
return true;
}
@Override
public boolean canUse(Race race) {
return race == Race.HIPPOGRIFF;
}
@Override
public Optional<Numeric> prepare(Pony player) {
return player.getAbilities().getActiveStat()
.map(stat -> (int)(stat.getWarmup() * getWarmupTime(player)))
.filter(i -> i >= 0)
.map(Numeric::new);
}
@Override
public Numeric.Serializer<Numeric> getSerializer() {
return Numeric.SERIALIZER;
}
@Override
public boolean apply(Pony player, Numeric data) {
float strength = 1 - MathHelper.clamp(data.type() / (float)getWarmupTime(player), 0, 1);
Random rng = player.asWorld().random;
playSounds(player, rng, strength);
if (!player.getPhysics().isFlying()) {
player.setAnimation(Animation.SPREAD_WINGS, Animation.Recipient.ANYONE);
}
int total = player.findAllEntitiesInRange((int)Math.max(1, 8 * strength)).mapToInt(e -> {
if (e instanceof LivingEntity living && !SpellType.SHIELD.isOn(e)) {
spookMob(player, living, strength);
return 1;
}
return 0;
}).sum();
if (total >= 20) {
UCriteria.SCREECH_TWENTY_MOBS.trigger(player.asEntity());
}
return true;
}
protected void playSounds(Pony player, Random rng, float strength) {
player.playSound(USounds.ENTITY_PLAYER_HIPPOGRIFF_SCREECH,
(1.2F + (rng.nextFloat() - 0.5F) / 2F) * strength,
1.1F + (rng.nextFloat() - 0.5F)
);
player.asWorld().emitGameEvent(player.asEntity(), GameEvent.ENTITY_ACTION, player.asEntity().getEyePos());
}
protected void spookMob(Pony player, LivingEntity living, float strength) {
boolean isEarthPony = EquinePredicates.PLAYER_EARTH.test(living);
boolean isBracing = isEarthPony && player.asEntity().isSneaking();
if (!isBracing) {
living.damage(player.damageOf(UDamageTypes.BAT_SCREECH, player), isEarthPony ? 0.1F : 0.3F);
if (living.getWorld().random.nextInt(MOB_SPOOK_PROBABILITY) == 0) {
RegistryUtils.pickRandom(living.getWorld(), UTags.SPOOKED_MOB_DROPS).ifPresent(drop -> {
living.dropStack(drop.getDefaultStack());
living.playSound(USounds.Vanilla.ENTITY_ITEM_PICKUP, 1, 0.1F);
UCriteria.SPOOK_MOB.trigger(player.asEntity());
});
}
}
Vec3d knockVec = player.getOriginVector().subtract(living.getPos()).multiply(strength);
living.takeKnockback((isBracing ? 0.2F : isEarthPony ? 0.3F : 0.5F) * strength, knockVec.getX(), knockVec.getZ());
if (!isEarthPony) {
living.addVelocity(0, 0.1 * strength, 0);
}
Living.updateVelocity(living);
}
@Override
public void warmUp(Pony player, AbilitySlot slot) {
}
@Override
public void coolDown(Pony player, AbilitySlot slot) {
for (int i = 0; i < 20; i++) {
player.addParticle(ParticleTypes.BUBBLE_POP, player.asEntity().getEyePos(),
VecHelper.supply(() -> (player.asWorld().getRandom().nextGaussian() - 0.5) * 0.3)
);
}
}
}

View file

@ -0,0 +1,112 @@
package com.minelittlepony.unicopia.ability;
import java.util.Comparator;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.UPOIs;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityGroup;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.event.GameEvent;
import net.minecraft.world.poi.PointOfInterestStorage.OccupationStatus;
public class SeaponySonarPulseAbility implements Ability<Hit> {
@Override
public int getWarmupTime(Pony player) {
return 10;
}
@Override
public int getCooldownTime(Pony player) {
return 100;
}
@Override
public boolean canUse(Race race) {
return race == Race.SEAPONY;
}
@Nullable
@Override
public Optional<Hit> prepare(Pony player) {
return Hit.INSTANCE;
}
@Override
public Hit.Serializer<Hit> getSerializer() {
return Hit.SERIALIZER;
}
@Override
public double getCostEstimate(Pony player) {
return 5;
}
@Override
public boolean apply(Pony player, Hit data) {
player.setAnimation(Animation.ARMS_UP, Animation.Recipient.ANYONE);
for (Entity target : player.findAllEntitiesInRange(64, e -> {
return (e instanceof LivingEntity && (e instanceof HostileEntity || ((LivingEntity)e).getGroup() == EntityGroup.AQUATIC)) && e.isSubmergedInWater();
}).sorted(Comparator.comparing(e -> e.distanceTo(player.asEntity()))).toList()) {
Vec3d offset = target.getPos().subtract(player.getOriginVector());
float distance = target.distanceTo(player.asEntity());
if (distance < 4) {
float scale = 1 - (distance/10F);
((LivingEntity)target).takeKnockback(0.7 * scale, -offset.x, -offset.z);
target.damage(target.getDamageSources().sonicBoom(player.asEntity()), 10 * scale);
} else {
emitPing(player, target.getPos(), 10, 1, 1.3F);
}
}
player.subtractEnergyCost(5);
if (player.asWorld() instanceof ServerWorld sw) {
sw.getPointOfInterestStorage().getNearestPosition(
type -> type.value() == UPOIs.CHESTS,
pos -> player.asWorld().getFluidState(pos).isIn(FluidTags.WATER), player.getOrigin(), 64, OccupationStatus.ANY)
.ifPresent(chestPos -> {
emitPing(player, chestPos.toCenterPos(), 20, 0.5F, 2F);
});
}
player.playSound(USounds.ENTITY_PLAYER_SEAPONY_SONAR, 1);
player.spawnParticles(UParticles.SHOCKWAVE, 1);
player.asEntity().emitGameEvent(GameEvent.INSTRUMENT_PLAY);
return true;
}
private void emitPing(Pony player, Vec3d pos, int delay, float volume, float pitch) {
AwaitTickQueue.scheduleTask(player.asWorld(), w -> {
ParticleUtils.spawnParticle(w, UParticles.SHOCKWAVE, pos, Vec3d.ZERO);
float loudness = Math.max(0, 1.4F - (float)Math.log10(player.getOriginVector().distanceTo(pos)));
w.playSound(null, pos.x, pos.y, pos.z, USounds.ENTITY_PLAYER_SEAPONY_SONAR, SoundCategory.AMBIENT, volume * loudness, pitch);
w.emitGameEvent(player.asEntity(), GameEvent.INSTRUMENT_PLAY, pos);
}, delay + (int)player.getOriginVector().distanceTo(pos));
}
@Override
public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getExertion().addPercent(6);
}
@Override
public void coolDown(Pony player, AbilitySlot slot) {
}
}

View file

@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class PegasusFlightToggleAbility implements Ability<Hit> { public class ToggleFlightAbility implements Ability<Hit> {
@Override @Override
public int getWarmupTime(Pony player) { public int getWarmupTime(Pony player) {

View file

@ -8,6 +8,7 @@ import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.block.state.StatePredicate; import com.minelittlepony.unicopia.block.state.StatePredicate;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -163,16 +164,19 @@ public class UnicornTeleportAbility implements Ability<Pos> {
Vec3d dest = destination.vec().add(offset); Vec3d dest = destination.vec().add(offset);
participant.teleport( dest = new Vec3d(dest.x, getTargetYPosition(participant.getEntityWorld(), BlockPos.ofFloored(dest), ShapeContext.of(participant)), dest.z);
dest.x,
getTargetYPosition(participant.getEntityWorld(), BlockPos.ofFloored(dest), ShapeContext.of(participant)), participant.teleport(dest.x, dest.y, dest.z);
dest.z
);
teleporter.subtractEnergyCost(distance); teleporter.subtractEnergyCost(distance);
participant.fallDistance /= distance; participant.fallDistance /= distance;
participant.getWorld().playSound(null, destination.pos(), USounds.ENTITY_PLAYER_UNICORN_TELEPORT, SoundCategory.PLAYERS, 1, 1); BlockPos blockPos = BlockPos.ofFloored(dest);
participant.getWorld().playSound(null, blockPos, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, SoundCategory.PLAYERS, 1, 1);
if (!participant.getEntityWorld().isInBuildLimit(blockPos)) {
UCriteria.TELEPORT_ABOVE_WORLD.trigger(teleporter.asEntity());
}
return true; return true;
} }

View file

@ -30,11 +30,17 @@ public class RageAbilitySpell extends AbstractSpell {
private int age; private int age;
private int ticksExtenguishing; private int ticksExtenguishing;
private int ticksToExtenguish;
public RageAbilitySpell(CustomisedSpellType<?> type) { public RageAbilitySpell(CustomisedSpellType<?> type) {
super(type); super(type);
setHidden(true); setHidden(true);
} }
public void setExtenguishing() {
ticksToExtenguish += 15;
}
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
@ -42,7 +48,7 @@ public class RageAbilitySpell extends AbstractSpell {
return false; return false;
} }
if (source.asEntity().isInsideWaterOrBubbleColumn()) { if (source.asEntity().isInsideWaterOrBubbleColumn() || source.asEntity().isFrozen() || ticksToExtenguish > 0) {
ticksExtenguishing++; ticksExtenguishing++;
source.playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 1); source.playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 1);
source.spawnParticles(ParticleTypes.CLOUD, 12); source.spawnParticles(ParticleTypes.CLOUD, 12);
@ -51,6 +57,10 @@ public class RageAbilitySpell extends AbstractSpell {
ticksExtenguishing = 0; ticksExtenguishing = 0;
} }
if (ticksToExtenguish > 0) {
ticksToExtenguish--;
}
if (ticksExtenguishing > 10) { if (ticksExtenguishing > 10) {
return false; return false;
} }
@ -94,7 +104,7 @@ public class RageAbilitySpell extends AbstractSpell {
} }
if (source instanceof Pony pony) { if (source instanceof Pony pony) {
if (source.isClient() && pony.asEntity().getAttackCooldownProgress(0) == 0) { if (pony.isClientPlayer() && pony.asEntity().getAttackCooldownProgress(0) == 0) {
InteractionManager.instance().playLoopingSound(source.asEntity(), InteractionManager.SOUND_KIRIN_RAGE, source.asWorld().random.nextLong()); InteractionManager.instance().playLoopingSound(source.asEntity(), InteractionManager.SOUND_KIRIN_RAGE, source.asWorld().random.nextLong());
} }
Bar energyBar = pony.getMagicalReserves().getEnergy(); Bar energyBar = pony.getMagicalReserves().getEnergy();

View file

@ -51,7 +51,7 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
); );
public static void load(Map<Identifier, SpellTraits> newRegistry) { public static void load(Map<Identifier, SpellTraits> newRegistry) {
REGISTRY = new HashMap<>(newRegistry); REGISTRY = newRegistry;
ITEMS.clear(); ITEMS.clear();
REGISTRY.forEach((itemId, traits) -> { REGISTRY.forEach((itemId, traits) -> {
Registries.ITEM.getOrEmpty(itemId).ifPresent(item -> { Registries.ITEM.getOrEmpty(itemId).ifPresent(item -> {
@ -228,10 +228,7 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
} }
public static Stream<Item> getItems(Trait trait) { public static Stream<Item> getItems(Trait trait) {
return REGISTRY.entrySet().stream() return ITEMS.getOrDefault(trait, List.of()).stream();
.filter(e -> e.getValue().get(trait) > 0)
.map(Map.Entry::getKey)
.flatMap(id -> Registries.ITEM.getOrEmpty(id).stream());
} }
public static Optional<SpellTraits> getEmbeddedTraits(ItemStack stack) { public static Optional<SpellTraits> getEmbeddedTraits(ItemStack stack) {

View file

@ -5,7 +5,9 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -26,7 +28,11 @@ import net.minecraft.resource.SinglePreparationResourceReloader;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper; import net.minecraft.util.JsonHelper;
import net.minecraft.util.profiler.Profiler; import net.minecraft.util.profiler.Profiler;
import net.minecraft.item.Item;
import net.minecraft.item.ItemConvertible;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Identifier, TraitLoader.TraitStream>> implements IdentifiableResourceReloadListener { public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Identifier, TraitLoader.TraitStream>> implements IdentifiableResourceReloadListener {
private static final Identifier ID = Unicopia.id("data/traits"); private static final Identifier ID = Unicopia.id("data/traits");
@ -77,9 +83,24 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
@Override @Override
protected void apply(Multimap<Identifier, TraitStream> prepared, ResourceManager manager, Profiler profiler) { protected void apply(Multimap<Identifier, TraitStream> prepared, ResourceManager manager, Profiler profiler) {
profiler.startTick(); profiler.startTick();
SpellTraits.load(prepared.values().stream()
Set<Map.Entry<TraitStream.Key, SpellTraits>> newRegistry = prepared.values().stream()
.flatMap(TraitStream::entries) .flatMap(TraitStream::entries)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, SpellTraits::union))); .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, SpellTraits::union))
.entrySet();
SpellTraits.load(Registries.ITEM.getEntrySet().stream()
.map(entry -> Map.entry(
entry.getKey().getValue(),
newRegistry.stream()
.filter(p -> p.getKey().test(entry.getValue()))
.map(Map.Entry::getValue)
.reduce(SpellTraits::union)
.orElse(SpellTraits.EMPTY)
))
.filter(entry -> !entry.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
profiler.endTick(); profiler.endTick();
} }
@ -88,14 +109,14 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
boolean replace(); boolean replace();
Stream<Map.Entry<Identifier, SpellTraits>> entries(); Stream<Map.Entry<Key, SpellTraits>> entries();
static TraitStream of(Identifier id, String pack, JsonObject json) { static TraitStream of(Identifier id, String pack, JsonObject json) {
if (json.has("items") && json.get("items").isJsonObject()) { if (json.has("items") && json.get("items").isJsonObject()) {
return new TraitMap(JsonHelper.getBoolean(json, "replace", false), return new TraitMap(JsonHelper.getBoolean(json, "replace", false),
Resources.GSON.getAdapter(TYPE).fromJsonTree(json.get("items")).entrySet().stream().collect(Collectors.toMap( Resources.GSON.getAdapter(TYPE).fromJsonTree(json.get("items")).entrySet().stream().collect(Collectors.toMap(
a -> Identifier.tryParse(a.getKey()), a -> Key.of(a.getKey()),
a -> SpellTraits.fromString(a.getValue()).orElse(SpellTraits.EMPTY) a -> SpellTraits.fromString(a.getValue()).orElse(SpellTraits.EMPTY)
)) ))
); );
@ -106,23 +127,16 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
SpellTraits.fromString(JsonHelper.getString(json, "traits")).orElse(SpellTraits.EMPTY), SpellTraits.fromString(JsonHelper.getString(json, "traits")).orElse(SpellTraits.EMPTY),
StreamSupport.stream(JsonHelper.getArray(json, "items").spliterator(), false) StreamSupport.stream(JsonHelper.getArray(json, "items").spliterator(), false)
.map(JsonElement::getAsString) .map(JsonElement::getAsString)
.map(Identifier::tryParse) .map(Key::of)
.filter(item -> {
if (item == null || !Registries.ITEM.containsId(item)) {
Unicopia.LOGGER.warn("Skipping unknown item {} in {}:{}", item, pack, id);
return false;
}
return true;
})
.collect(Collectors.toSet()) .collect(Collectors.toSet())
); );
} }
record TraitMap ( record TraitMap (
boolean replace, boolean replace,
Map<Identifier, SpellTraits> items) implements TraitStream { Map<Key, SpellTraits> items) implements TraitStream {
@Override @Override
public Stream<Entry<Identifier, SpellTraits>> entries() { public Stream<Entry<Key, SpellTraits>> entries() {
return items.entrySet().stream(); return items.entrySet().stream();
} }
} }
@ -130,12 +144,32 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
record TraitSet ( record TraitSet (
boolean replace, boolean replace,
SpellTraits traits, SpellTraits traits,
Set<Identifier> items) implements TraitStream { Set<Key> items) implements TraitStream {
@Override @Override
public Stream<Entry<Identifier, SpellTraits>> entries() { public Stream<Entry<Key, SpellTraits>> entries() {
return items().stream().map(item -> Map.entry(item, traits())); return items().stream().map(item -> Map.entry(item, traits()));
} }
} }
}
interface Key extends Predicate<ItemConvertible> {
static Key of(String s) {
return s.startsWith("#") ? new Tag(TagKey.of(RegistryKeys.ITEM, Identifier.tryParse(s.substring(1)))) : new Id(Identifier.tryParse(s));
}
record Tag(TagKey<Item> tag) implements Key {
@SuppressWarnings("deprecation")
@Override
public boolean test(ItemConvertible item) {
return item.asItem().getRegistryEntry().isIn(tag);
}
}
record Id(Identifier id) implements Key {
@Override
public boolean test(ItemConvertible item) {
return Objects.equals(id, Registries.ITEM.getId(item.asItem()));
}
}
}
}
} }

View file

@ -27,6 +27,7 @@ public interface UCriteria {
CustomEventCriterion.Trigger POWER_UP_HEART = CUSTOM_EVENT.createTrigger("power_up_heart"); CustomEventCriterion.Trigger POWER_UP_HEART = CUSTOM_EVENT.createTrigger("power_up_heart");
CustomEventCriterion.Trigger SPLIT_SEA = CUSTOM_EVENT.createTrigger("split_sea"); CustomEventCriterion.Trigger SPLIT_SEA = CUSTOM_EVENT.createTrigger("split_sea");
CustomEventCriterion.Trigger RIDE_BALLOON = CUSTOM_EVENT.createTrigger("ride_balloon"); CustomEventCriterion.Trigger RIDE_BALLOON = CUSTOM_EVENT.createTrigger("ride_balloon");
CustomEventCriterion.Trigger TELEPORT_ABOVE_WORLD = CUSTOM_EVENT.createTrigger("teleport_above_world");
static void bootstrap() { } static void bootstrap() { }
} }

View file

@ -41,6 +41,10 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
@Override @Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (state.get(PERSISTENT)) {
return state;
}
if (world instanceof ServerWorld sw) { if (world instanceof ServerWorld sw) {
ZapAppleStageStore store = ZapAppleStageStore.get(sw); ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage(); ZapAppleStageStore.Stage currentStage = store.getStage();
@ -56,7 +60,9 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random); super.scheduledTick(state, world, pos, random);
tryAdvanceStage(state, world, pos, random); tryAdvanceStage(state, world, pos, random);
world.scheduleBlockTick(pos, this, 1); if (!state.get(PERSISTENT)) {
world.scheduleBlockTick(pos, this, 1);
}
} }
private void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) { private void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) {

View file

@ -0,0 +1,64 @@
package com.minelittlepony.unicopia.block;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.item.UItems;
import net.minecraft.block.Block;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
import net.minecraft.block.DoorBlock;
import net.minecraft.block.enums.DoubleBlockHalf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
public class CrystalDoorBlock extends DoorBlock {
public CrystalDoorBlock(Settings settings, BlockSetType blockSet) {
super(settings, blockSet);
}
@Override
public void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
boolean powered = world.isReceivingRedstonePower(pos) || world.isReceivingRedstonePower(pos.offset(state.get(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
if (!getDefaultState().isOf(sourceBlock) && powered != state.get(POWERED)) {
if (powered) {
state = state.cycle(OPEN);
playOpenCloseSound(null, world, pos, state.get(OPEN));
world.emitGameEvent(null, state.get(OPEN) ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
}
world.setBlockState(pos, state.with(POWERED, powered), Block.NOTIFY_LISTENERS);
}
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!EquineContext.of(player).getCompositeRace().any(Race::canCast)) {
if (!player.getStackInHand(hand).isOf(UItems.MEADOWBROOKS_STAFF)) {
playOpenCloseSound(player, world, pos, false);
return ActionResult.FAIL;
} else {
world.playSound(player, pos, USounds.ENTITY_CRYSTAL_SHARDS_AMBIENT, SoundCategory.BLOCKS, 1, world.getRandom().nextFloat() * 0.1F + 0.9F);
}
}
return super.onUse(state, world, pos, player, hand, hit);
}
private void playOpenCloseSound(@Nullable Entity entity, World world, BlockPos pos, boolean open) {
world.playSound(entity, pos, open ? getBlockSetType().doorOpen() : getBlockSetType().doorClose(), SoundCategory.BLOCKS, 1, world.getRandom().nextFloat() * 0.1f + 0.9f);
}
}

View file

@ -0,0 +1,182 @@
package com.minelittlepony.unicopia.block;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.item.BedsheetsItem;
import com.minelittlepony.unicopia.util.VoxelShapeUtil;
import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BedBlockEntity;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.enums.BedPart;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.DyeColor;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class FancyBedBlock extends BedBlock {
private static final List<Function<Direction, VoxelShape>> SHAPES = List.of(
VoxelShapeUtil.rotator(VoxelShapes.union(
createCuboidShape(0, 3, 1, 16, 9, 16),
createCuboidShape(-0.5, 0, 1, 1.5, 13, 4),
createCuboidShape(14.5, 0, 1, 16.5, 13, 4),
createCuboidShape(1.5, 1, 0, 14.5, 16, 3)
)),
VoxelShapeUtil.rotator(VoxelShapes.union(
createCuboidShape(0, 3, 0, 16, 9, 16),
createCuboidShape(-0.5, 0, -1, 2.5, 10, 2),
createCuboidShape(13.5, 0, -1, 16.5, 10, 2),
createCuboidShape(1.5, 1, -2, 14.5, 12, 1)
))
);
private final String base;
public FancyBedBlock(String base, Settings settings) {
super(DyeColor.WHITE, settings);
this.base = base;
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return SHAPES.get(state.get(PART).ordinal()).apply(BedBlock.getOppositePartDirection(state));
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new Tile(pos, state);
}
public static void setBedPattern(World world, BlockPos pos, SheetPattern pattern) {
world.getBlockEntity(pos, UBlockEntities.FANCY_BED).ifPresent(tile -> {
ItemStack stack = BedsheetsItem.forPattern(tile.getPattern()).getDefaultStack();
if (!stack.isEmpty()) {
Block.dropStack(world, pos, stack);
}
tile.setPattern(pattern);
BlockState state = tile.getCachedState();
BlockPos other = pos.offset(getDirectionTowardsOtherPart(state.get(PART), state.get(FACING)));
world.getBlockEntity(other, UBlockEntities.FANCY_BED).ifPresent(tile2 -> {
tile2.setPattern(pattern);
});
});
}
private static Direction getDirectionTowardsOtherPart(BedPart part, Direction direction) {
return part == BedPart.FOOT ? direction : direction.getOpposite();
}
public static class Tile extends BedBlockEntity {
private SheetPattern pattern = SheetPattern.NONE;
public Tile(BlockPos pos, BlockState state) {
super(pos, state);
}
@Override
public BlockEntityType<?> getType() {
return UBlockEntities.FANCY_BED;
}
@Override
public void readNbt(NbtCompound nbt) {
pattern = SheetPattern.byId(nbt.getString("pattern"));
}
@Override
protected void writeNbt(NbtCompound nbt) {
nbt.putString("pattern", pattern.asString());
}
@Override
public NbtCompound toInitialChunkDataNbt() {
return createNbt();
}
public String getBase() {
return ((FancyBedBlock)getCachedState().getBlock()).base;
}
public void setPattern(SheetPattern pattern) {
this.pattern = pattern;
markDirty();
if (world instanceof ServerWorld sw) {
sw.getChunkManager().markForUpdate(getPos());
}
}
public SheetPattern getPattern() {
return pattern;
}
}
public enum SheetPattern implements StringIdentifiable {
NONE(DyeColor.WHITE),
LIGHT_GRAY(DyeColor.LIGHT_GRAY),
GRAY(DyeColor.GRAY),
BLACK(DyeColor.BLACK),
BROWN(DyeColor.BROWN),
RED(DyeColor.RED),
ORANGE(DyeColor.ORANGE),
YELLOW(DyeColor.YELLOW),
LIME(DyeColor.LIME),
GREEN(DyeColor.GREEN),
CYAN(DyeColor.CYAN),
LIGHT_BLUE(DyeColor.LIGHT_BLUE),
BLUE(DyeColor.BLUE),
PURPLE(DyeColor.PURPLE),
MAGENTA(DyeColor.MAGENTA),
PINK(DyeColor.PINK),
APPLE(null),
BARS(null),
CHECKER(null),
KELP(null),
RAINBOW(null),
RAINBOW_BPW(null),
RAINBOW_BPY(null),
RAINBOW_PBG(null),
RAINBOW_PWR(null);
@SuppressWarnings("deprecation")
public static final EnumCodec<SheetPattern> CODEC = StringIdentifiable.createCodec(SheetPattern::values);
private final String name = name().toLowerCase(Locale.ROOT);
@Nullable
private final DyeColor color;
SheetPattern(@Nullable DyeColor color) {
this.color = color;
}
@Nullable
public DyeColor getColor() {
return color;
}
@Override
public String asString() {
return name;
}
@SuppressWarnings("deprecation")
public static SheetPattern byId(String id) {
return CODEC.byId(id, NONE);
}
}
}

View file

@ -0,0 +1,74 @@
package com.minelittlepony.unicopia.block;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.Waterloggable;
import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.fluid.FluidState;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess;
public class ShellsBlock extends Block implements Waterloggable {
public static final BooleanProperty WATERLOGGED = Properties.WATERLOGGED;
public static final IntProperty COUNT = IntProperty.of("count", 1, 4);
private static final VoxelShape SHAPE = Block.createCuboidShape(0, 0, 0, 16, 1, 16);
public ShellsBlock(Settings settings) {
super(settings);
}
@Override
@Deprecated
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return SHAPE;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder);
builder.add(COUNT, WATERLOGGED);
}
@Override
@Nullable
public BlockState getPlacementState(ItemPlacementContext ctx) {
return getDefaultState().with(WATERLOGGED, ctx.getWorld().getFluidState(ctx.getBlockPos()).getFluid() == Fluids.WATER);
}
@Deprecated
@Override
public FluidState getFluidState(BlockState state) {
if (state.get(WATERLOGGED).booleanValue()) {
return Fluids.WATER.getStill(false);
}
return super.getFluidState(state);
}
@Deprecated
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (state.get(WATERLOGGED).booleanValue()) {
world.scheduleFluidTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world));
}
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos);
}
@Override
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) {
return (type == NavigationType.WATER) == world.getFluidState(pos).isIn(FluidTags.WATER);
}
}

View file

@ -1,7 +1,10 @@
package com.minelittlepony.unicopia.block; package com.minelittlepony.unicopia.block;
import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import org.joml.Vector3f;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import net.minecraft.block.Block; import net.minecraft.block.Block;
@ -14,11 +17,14 @@ import net.minecraft.entity.mob.SlimeEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.particle.BlockStateParticleEffect; import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.particle.DustParticleEffect;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.EnumProperty; import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.util.StringIdentifiable; import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
@ -34,24 +40,29 @@ import net.minecraft.world.WorldAccess;
import net.minecraft.world.WorldView; import net.minecraft.world.WorldView;
public class SlimePustuleBlock extends Block { public class SlimePustuleBlock extends Block {
static final EnumProperty<Shape> SHAPE = EnumProperty.of("shape", Shape.class); private static final EnumProperty<Shape> SHAPE = EnumProperty.of("shape", Shape.class);
static final VoxelShape SHAFT_SHAPE = Block.createCuboidShape(7.5, 0, 7.5, 8.5, 16, 8.5); private static final BooleanProperty POWERED = Properties.POWERED;
static final VoxelShape DRIP_SHAPE = VoxelShapes.union( private static final Direction[] DIRECTIONS = Arrays.stream(Direction.values())
.filter(direction -> direction != Direction.UP)
.toArray(Direction[]::new);
private static final VoxelShape SHAFT_SHAPE = Block.createCuboidShape(7.5, 0, 7.5, 8.5, 16, 8.5);
private static final VoxelShape DRIP_SHAPE = VoxelShapes.union(
Block.createCuboidShape(7, 10, 7, 9, 16, 9), Block.createCuboidShape(7, 10, 7, 9, 16, 9),
Block.createCuboidShape(3, 15, 4, 9, 16, 10), Block.createCuboidShape(3, 15, 4, 9, 16, 10),
Block.createCuboidShape(7, 15, 7, 12, 16, 12) Block.createCuboidShape(7, 15, 7, 12, 16, 12)
); );
static final VoxelShape BULB_SHAPE = VoxelShapes.union( private static final VoxelShape BULB_SHAPE = VoxelShapes.union(
Block.createCuboidShape(4, 1, 4, 12, 10, 12), Block.createCuboidShape(4, 1, 4, 12, 10, 12),
Block.createCuboidShape(5, 10, 5, 11, 13, 11), Block.createCuboidShape(5, 10, 5, 11, 13, 11),
Block.createCuboidShape(6, 13, 6, 10, 15, 10), Block.createCuboidShape(6, 13, 6, 10, 15, 10),
Block.createCuboidShape(7, 13, 7, 9, 20, 9) Block.createCuboidShape(7, 13, 7, 9, 20, 9)
); );
static final VoxelShape CAP_SHAPE = VoxelShapes.union(SHAFT_SHAPE, DRIP_SHAPE); private static final VoxelShape CAP_SHAPE = VoxelShapes.union(SHAFT_SHAPE, DRIP_SHAPE);
private static final Vector3f DUST_COLOR = new Vector3f(1, 0.2F, 0.1F);
public SlimePustuleBlock(Settings settings) { public SlimePustuleBlock(Settings settings) {
super(settings.ticksRandomly()); super(settings.ticksRandomly());
setDefaultState(getDefaultState().with(SHAPE, Shape.DRIP)); setDefaultState(getDefaultState().with(SHAPE, Shape.DRIP).with(POWERED, false));
} }
@Override @Override
@ -68,6 +79,17 @@ public class SlimePustuleBlock extends Block {
public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random random) { public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random random) {
super.randomDisplayTick(state, world, pos, random); super.randomDisplayTick(state, world, pos, random);
if (state.get(POWERED)) {
VoxelShape shape = state.getCullingShape(world, pos);
float x = (float)MathHelper.lerp(random.nextFloat(), shape.getMin(Axis.X), shape.getMax(Axis.X));
float z = (float)MathHelper.lerp(random.nextFloat(), shape.getMin(Axis.Z), shape.getMax(Axis.Z));
world.addParticle(new DustParticleEffect(DUST_COLOR, 1),
pos.getX() + x,
pos.getY() + random.nextDouble(),
pos.getZ() + z, 0, 0, 0);
}
if (random.nextInt(15) == 0) { if (random.nextInt(15) == 0) {
VoxelShape shape = state.getCullingShape(world, pos); VoxelShape shape = state.getCullingShape(world, pos);
float x = (float)MathHelper.lerp(random.nextFloat(), shape.getMin(Axis.X), shape.getMax(Axis.X)); float x = (float)MathHelper.lerp(random.nextFloat(), shape.getMin(Axis.X), shape.getMax(Axis.X));
@ -82,7 +104,7 @@ public class SlimePustuleBlock extends Block {
@Deprecated @Deprecated
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (state.get(SHAPE) == Shape.POD) { if (state.get(SHAPE) == Shape.POD && random.nextInt(130) == 0) {
SlimeEntity slime = EntityType.SLIME.create(world); SlimeEntity slime = EntityType.SLIME.create(world);
slime.setSize(1, true); slime.setSize(1, true);
slime.setPosition(pos.toCenterPos()); slime.setPosition(pos.toCenterPos());
@ -132,7 +154,7 @@ public class SlimePustuleBlock extends Block {
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(SHAPE); builder.add(SHAPE, POWERED);
} }
@Deprecated @Deprecated
@ -154,20 +176,66 @@ public class SlimePustuleBlock extends Block {
Shape currentShape = state.get(SHAPE); Shape currentShape = state.get(SHAPE);
if (direction == Direction.DOWN && (currentShape == Shape.CAP || currentShape == Shape.STRING)) { if (direction == Direction.DOWN && (currentShape == Shape.CAP || currentShape == Shape.STRING)) {
return state; return state.with(POWERED, getReceivedRedstonePower(world, pos) > 0);
} }
Shape shape = determineShape(world, pos); Shape shape = determineShape(world, pos);
return state.with(SHAPE, shape); return state.with(SHAPE, shape).with(POWERED, getReceivedRedstonePower(world, pos) > 0);
} }
return state; return state.with(POWERED, getReceivedRedstonePower(world, pos) > 0);
} }
@Override @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { public BlockState getPlacementState(ItemPlacementContext ctx) {
Shape shape = determineShape(ctx.getWorld(), ctx.getBlockPos()); Shape shape = determineShape(ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(SHAPE, shape == Shape.STRING ? Shape.POD : shape); return super.getPlacementState(ctx)
.with(SHAPE, shape == Shape.STRING ? Shape.POD : shape)
.with(POWERED, getReceivedRedstonePower(ctx.getWorld(), ctx.getBlockPos()) > 0);
}
@Override
@Deprecated
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if (state.isOf(this) && newState.isOf(this) && state.get(POWERED) != newState.get(POWERED)) {
world.updateNeighbor(pos.up(), this, pos);
}
}
@Override
@Deprecated
public boolean emitsRedstonePower(BlockState state) {
return state.get(POWERED);
}
@Override
@Deprecated
public int getWeakRedstonePower(BlockState state, BlockView world, BlockPos pos, Direction direction) {
if (direction == Direction.DOWN && emitsRedstonePower(state)) {
return 15;
}
return 0;
}
@Override
@Deprecated
public int getStrongRedstonePower(BlockState state, BlockView world, BlockPos pos, Direction direction) {
if (direction == Direction.DOWN && emitsRedstonePower(state)) {
return 15;
}
return 0;
}
private int getReceivedRedstonePower(BlockView world, BlockPos pos) {
int power = 0;
for (Direction direction : DIRECTIONS) {
power = Math.max(power, world.getBlockState(pos.offset(direction)).getStrongRedstonePower(world, pos, direction));
if (power >= 15) {
return Math.min(15, power);
}
}
return Math.min(15, power);
} }
private Shape determineShape(WorldAccess world, BlockPos pos) { private Shape determineShape(WorldAccess world, BlockPos pos) {

View file

@ -11,8 +11,8 @@ import net.minecraft.world.WorldAccess;
public class StableDoorBlock extends DoorBlock { public class StableDoorBlock extends DoorBlock {
public StableDoorBlock(Settings settings) { public StableDoorBlock(Settings settings, BlockSetType blockSet) {
super(settings, BlockSetType.OAK); super(settings, blockSet);
} }
@Override @Override
@ -21,6 +21,12 @@ public class StableDoorBlock extends DoorBlock {
if (direction.getAxis() == Direction.Axis.Y && half == DoubleBlockHalf.LOWER == (direction == Direction.UP)) { if (direction.getAxis() == Direction.Axis.Y && half == DoubleBlockHalf.LOWER == (direction == Direction.UP)) {
if (neighborState.isOf(this) && neighborState.get(HALF) != half) { if (neighborState.isOf(this) && neighborState.get(HALF) != half) {
state = state
.with(FACING, neighborState.get(FACING))
.with(HINGE, neighborState.get(HINGE));
if (half == DoubleBlockHalf.UPPER && direction == Direction.DOWN && !state.get(POWERED)) {
state = state.with(OPEN, neighborState.get(OPEN));
}
return state; return state;
} }

View file

@ -0,0 +1,151 @@
package com.minelittlepony.unicopia.block;
import java.util.Collection;
import java.util.function.Supplier;
import com.minelittlepony.unicopia.ability.EarthPonyGrowAbility;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ConnectingBlock;
import net.minecraft.block.Fertilizable;
import net.minecraft.entity.SpawnReason;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.DirectionProperty;
import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.state.property.Property;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.WorldView;
public class ThornBlock extends ConnectingBlock implements EarthPonyGrowAbility.Growable, Fertilizable {
static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values();
static final DirectionProperty FACING = Properties.FACING;
static final int MAX_DISTANCE = 25;
static final int MAX_AGE = 4;
static final IntProperty DISTANCE = IntProperty.of("distance", 0, MAX_DISTANCE);
static final IntProperty AGE = IntProperty.of("age", 0, MAX_AGE);
private final Supplier<Block> bud;
public ThornBlock(Settings settings, Supplier<Block> bud) {
super(0.125F, settings);
this.bud = bud;
PROPERTIES.forEach(property -> setDefaultState(getDefaultState().with(property, false)));
setDefaultState(getDefaultState()
.with(FACING, Direction.DOWN)
.with(DISTANCE, 0)
.with(AGE, 0)
.with(DOWN, true)
);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(PROPERTIES.toArray(Property[]::new));
builder.add(FACING, DISTANCE, AGE);
}
@Override
@Deprecated
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (state.get(AGE) == MAX_AGE
&& random.nextInt(1200) == 0
&& world.isPlayerInRange(pos.getX(), pos.getY(), pos.getZ(), 3)) {
UEntities.LOOT_BUG.spawn(world, pos, SpawnReason.NATURAL);
}
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (!state.canPlaceAt(world, pos)) {
world.breakBlock(pos, true);
}
}
@Override
public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random random) {
if (state.get(AGE) == MAX_AGE && world.isPlayerInRange(pos.getX(), pos.getY(), pos.getZ(), 3)) {
Vec3d particlePos = pos.toCenterPos().add(VecHelper.supply(() -> random.nextTriangular(0, 0.5)));
world.addImportantParticle(ParticleTypes.ASH, particlePos.x, particlePos.y, particlePos.z, 0, 0, 0);
}
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (direction == state.get(FACING) && !state.canPlaceAt(world, pos)) {
world.scheduleBlockTick(pos, this, 1);
}
return state;
}
@Override
public boolean canPlaceAt(BlockState state, WorldView world, BlockPos pos) {
Direction facing = state.get(FACING);
BlockState neighborState = world.getBlockState(pos.offset(facing));
return (facing == Direction.DOWN && state.get(DISTANCE) == 0 && neighborState.isIn(BlockTags.DIRT))
|| neighborState.isOf(this)
|| neighborState.isOf(bud.get());
}
@Override
public boolean grow(World world, BlockState state, BlockPos pos) {
if (state.get(DISTANCE) >= MAX_DISTANCE) {
return false;
}
world.setBlockState(pos, state.with(AGE, Math.min(state.get(AGE) + 1, MAX_AGE)));
return FACING_PROPERTIES.keySet().stream()
.filter(direction -> isConnected(state, world.getBlockState(pos.offset(direction)), direction))
.map(direction -> {
BlockPos p = pos.offset(direction);
BlockState s = world.getBlockState(p);
if (s.isAir()) {
// sprout a new branch for cut off nodes
world.setBlockState(p, bud.get().getDefaultState()
.with(FACING, direction.getOpposite())
.with(DISTANCE, state.get(DISTANCE) + 1)
);
return true;
}
return ((EarthPonyGrowAbility.Growable)s.getBlock()).grow(world, s, p);
}).reduce(false, Boolean::logicalOr);
}
@Override
public boolean isFertilizable(WorldView world, BlockPos pos, BlockState state) {
return true;
}
@Override
public boolean canGrow(World world, Random random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void grow(ServerWorld world, Random random, BlockPos pos, BlockState state) {
grow(world, state, pos);
}
private boolean isConnected(BlockState state, BlockState neighborState, Direction direction) {
if (!state.get(FACING_PROPERTIES.get(direction))) {
return false;
}
if (neighborState.isAir()) {
return true;
}
return neighborState.getBlock() instanceof EarthPonyGrowAbility.Growable
&& (neighborState.isOf(this) || neighborState.isOf(bud.get()))
&& neighborState.get(FACING) == direction.getOpposite();
}
}

View file

@ -0,0 +1,116 @@
package com.minelittlepony.unicopia.block;
import java.util.Optional;
import java.util.stream.Stream;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.EarthPonyGrowAbility;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.Fertilizable;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.DirectionProperty;
import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.WorldView;
public class ThornBudBlock extends Block implements EarthPonyGrowAbility.Growable, Fertilizable {
static final DirectionProperty FACING = Properties.FACING;
static final int MAX_DISTANCE = 25;
static final IntProperty DISTANCE = IntProperty.of("distance", 0, MAX_DISTANCE);
private final BlockState branchState;
public ThornBudBlock(Settings settings, BlockState branchState) {
super(settings);
setDefaultState(getDefaultState().with(FACING, Direction.DOWN).with(DISTANCE, 0));
this.branchState = branchState;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(FACING, DISTANCE);
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (random.nextInt(50) == 0) {
grow(world, state, pos);
}
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (direction == state.get(FACING) && !(neighborState.isOf(this) || neighborState.isOf(branchState.getBlock()))) {
return Blocks.AIR.getDefaultState();
}
return state;
}
@Override
public boolean grow(World world, BlockState state, BlockPos pos) {
if (state.get(DISTANCE) >= MAX_DISTANCE) {
return false;
}
return pickGrowthDirection(world, state, pos).map(randomDirection -> {
BlockPos p = pos.offset(randomDirection);
if (!canReplace(world.getBlockState(p))) {
return false;
}
world.playSound(null, pos, USounds.Vanilla.ITEM_BONE_MEAL_USE, SoundCategory.BLOCKS);
world.setBlockState(pos, branchState
.with(FACING, state.get(FACING))
.with(ThornBlock.FACING_PROPERTIES.get(state.get(FACING)), true)
.with(ThornBlock.FACING_PROPERTIES.get(randomDirection), true));
world.setBlockState(p, getDefaultState()
.with(FACING, randomDirection.getOpposite())
.with(DISTANCE, state.get(DISTANCE) + 1)
);
return true;
}).orElse(false);
}
protected boolean canReplace(BlockState state) {
return state.isReplaceable();
}
private static Optional<Direction> pickGrowthDirection(World world, BlockState state, BlockPos pos) {
Direction excluded = state.get(FACING);
return Util.getRandomOrEmpty(ThornBlock.FACING_PROPERTIES.keySet().stream()
.filter(direction -> direction != excluded)
.flatMap(direction -> getByWeight(direction, excluded))
.toList(), world.getRandom());
}
private static Stream<Direction> getByWeight(Direction input, Direction excluded) {
return Stream.generate(() -> input)
.limit(input.getAxis() == excluded.getAxis() ? 6L : input.getAxis() == Direction.Axis.Y ? 1L : 3L);
}
@Override
public boolean isFertilizable(WorldView world, BlockPos pos, BlockState state) {
return true;
}
@Override
public boolean canGrow(World world, Random random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void grow(ServerWorld world, Random random, BlockPos pos, BlockState state) {
grow(world, state, pos);
}
}

View file

@ -1,16 +1,19 @@
package com.minelittlepony.unicopia.block; package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.block.cloud.CloudBedBlock; import com.minelittlepony.unicopia.block.cloud.CloudBedBlock;
import com.minelittlepony.unicopia.block.cloud.CloudChestBlock;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.entity.BlockEntityType.Builder; import net.minecraft.block.entity.BlockEntityType.Builder;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.registry.Registry; import net.minecraft.registry.Registry;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
public interface UBlockEntities { public interface UBlockEntities {
BlockEntityType<WeatherVaneBlock.WeatherVane> WEATHER_VANE = create("weather_vane", BlockEntityType.Builder.create(WeatherVaneBlock.WeatherVane::new, UBlocks.WEATHER_VANE)); BlockEntityType<WeatherVaneBlock.WeatherVane> WEATHER_VANE = create("weather_vane", BlockEntityType.Builder.create(WeatherVaneBlock.WeatherVane::new, UBlocks.WEATHER_VANE));
BlockEntityType<CloudBedBlock.Tile> CLOUD_BED = create("cloud_bed", BlockEntityType.Builder.create(CloudBedBlock.Tile::new, UBlocks.CLOUD_BED)); BlockEntityType<CloudBedBlock.Tile> FANCY_BED = create("fancy_bed", BlockEntityType.Builder.create(CloudBedBlock.Tile::new, UBlocks.CLOTH_BED, UBlocks.CLOUD_BED));
BlockEntityType<ChestBlockEntity> CLOUD_CHEST = create("cloud_chest", BlockEntityType.Builder.create(CloudChestBlock.TileData::new, UBlocks.CLOUD_CHEST));
static <T extends BlockEntity> BlockEntityType<T> create(String id, Builder<T> builder) { static <T extends BlockEntity> BlockEntityType<T> create(String id, Builder<T> builder) {
return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, builder.build(null)); return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, builder.build(null));

View file

@ -10,10 +10,16 @@ import com.minelittlepony.unicopia.block.cloud.CloudSlabBlock;
import com.minelittlepony.unicopia.block.cloud.CloudStairsBlock; import com.minelittlepony.unicopia.block.cloud.CloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.CompactedCloudBlock; import com.minelittlepony.unicopia.block.cloud.CompactedCloudBlock;
import com.minelittlepony.unicopia.block.cloud.NaturalCloudBlock; import com.minelittlepony.unicopia.block.cloud.NaturalCloudBlock;
import com.minelittlepony.unicopia.block.cloud.OrientedCloudBlock;
import com.minelittlepony.unicopia.block.cloud.PoreousCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.ShapingBenchBlock;
import com.minelittlepony.unicopia.block.cloud.CloudBedBlock; import com.minelittlepony.unicopia.block.cloud.CloudBedBlock;
import com.minelittlepony.unicopia.block.cloud.CloudBlock; import com.minelittlepony.unicopia.block.cloud.CloudChestBlock;
import com.minelittlepony.unicopia.block.cloud.CloudDoorBlock;
import com.minelittlepony.unicopia.block.cloud.CloudLike;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudBlock; import com.minelittlepony.unicopia.block.cloud.SoggyCloudBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock; import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock; import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBlockItem; import com.minelittlepony.unicopia.item.cloud.CloudBlockItem;
@ -130,6 +136,9 @@ public interface UBlocks {
SegmentedCropBlock OATS_STEM = register("oats_stem", OATS.createNext(5)); SegmentedCropBlock OATS_STEM = register("oats_stem", OATS.createNext(5));
SegmentedCropBlock OATS_CROWN = register("oats_crown", OATS_STEM.createNext(5)); SegmentedCropBlock OATS_CROWN = register("oats_crown", OATS_STEM.createNext(5));
Block PLUNDER_VINE = register("plunder_vine", new ThornBlock(Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).ticksRandomly().sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), () -> UBlocks.PLUNDER_VINE_BUD));
Block PLUNDER_VINE_BUD = register("plunder_vine_bud", new ThornBudBlock(Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).nonOpaque().ticksRandomly().sounds(BlockSoundGroup.GRASS).pistonBehavior(PistonBehavior.DESTROY), PLUNDER_VINE.getDefaultState()));
Block CHITIN = register("chitin", new SnowyBlock(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool().ticksRandomly().sounds(BlockSoundGroup.CORAL)), ItemGroups.NATURAL); Block CHITIN = register("chitin", new SnowyBlock(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool().ticksRandomly().sounds(BlockSoundGroup.CORAL)), ItemGroups.NATURAL);
Block SURFACE_CHITIN = register("surface_chitin", new GrowableBlock(Settings.copy(CHITIN), () -> CHITIN), ItemGroups.NATURAL); Block SURFACE_CHITIN = register("surface_chitin", new GrowableBlock(Settings.copy(CHITIN), () -> CHITIN), ItemGroups.NATURAL);
Block CHISELLED_CHITIN = register("chiselled_chitin", new Block(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool()), ItemGroups.BUILDING_BLOCKS); Block CHISELLED_CHITIN = register("chiselled_chitin", new Block(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool()), ItemGroups.BUILDING_BLOCKS);
@ -141,27 +150,61 @@ public interface UBlocks {
Block MYSTERIOUS_EGG = register("mysterious_egg", new PileBlock(Settings.copy(Blocks.SLIME_BLOCK), PileBlock.MYSTERIOUS_EGG_SHAPES), ItemGroups.NATURAL); Block MYSTERIOUS_EGG = register("mysterious_egg", new PileBlock(Settings.copy(Blocks.SLIME_BLOCK), PileBlock.MYSTERIOUS_EGG_SHAPES), ItemGroups.NATURAL);
Block SLIME_PUSTULE = register("slime_pustule", new SlimePustuleBlock(Settings.copy(Blocks.SLIME_BLOCK)), ItemGroups.NATURAL); Block SLIME_PUSTULE = register("slime_pustule", new SlimePustuleBlock(Settings.copy(Blocks.SLIME_BLOCK)), ItemGroups.NATURAL);
Block SHAPING_BENCH = register("shaping_bench", new ShapingBenchBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.FUNCTIONAL);
Block CLOUD = register("cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL), true, Block CLOUD = register("cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL), true,
() -> UBlocks.SOGGY_CLOUD, () -> UBlocks.SOGGY_CLOUD,
() -> UBlocks.COMPACTED_CLOUD), ItemGroups.NATURAL); () -> UBlocks.COMPACTED_CLOUD), ItemGroups.NATURAL);
Block COMPACTED_CLOUD = register("compacted_cloud", new CompactedCloudBlock(CLOUD.getDefaultState()));
Block CLOUD_SLAB = register("cloud_slab", new CloudSlabBlock(Settings.copy(CLOUD), true, () -> UBlocks.SOGGY_CLOUD_SLAB), ItemGroups.NATURAL); Block CLOUD_SLAB = register("cloud_slab", new CloudSlabBlock(Settings.copy(CLOUD), true, () -> UBlocks.SOGGY_CLOUD_SLAB), ItemGroups.NATURAL);
Block CLOUD_STAIRS = register("cloud_stairs", new CloudStairsBlock(CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.SOGGY_CLOUD_STAIRS), ItemGroups.NATURAL); PoreousCloudStairsBlock CLOUD_STAIRS = register("cloud_stairs", new PoreousCloudStairsBlock(CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.SOGGY_CLOUD_STAIRS), ItemGroups.NATURAL);
Block COMPACTED_CLOUD = register("compacted_cloud", new CompactedCloudBlock(Settings.copy(CLOUD)));
Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(Settings.copy(CLOUD).requiresTool(), false, Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false,
null, null,
() -> UBlocks.COMPACTED_CLOUD_PLANKS), ItemGroups.BUILDING_BLOCKS); () -> UBlocks.COMPACTED_CLOUD_PLANKS), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_PLANKS_SLAB = register("cloud_planks_slab", new CloudSlabBlock(Settings.copy(CLOUD_PLANKS), false, null), ItemGroups.BUILDING_BLOCKS); Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(CLOUD_PLANKS.getDefaultState()));
Block CLOUD_PLANKS_STAIRS = register("cloud_planks_stairs", new CloudStairsBlock(CLOUD_PLANKS.getDefaultState(), Settings.copy(CLOUD_PLANKS), null), ItemGroups.BUILDING_BLOCKS); Block CLOUD_PLANK_SLAB = register("cloud_plank_slab", new CloudSlabBlock(Settings.copy(CLOUD_PLANKS), false, null), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(Settings.copy(CLOUD_PLANKS))); Block CLOUD_PLANK_STAIRS = register("cloud_plank_stairs", new CloudStairsBlock(CLOUD_PLANKS.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICKS = register("cloud_bricks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.6F).requiresTool().solid(), false,
null,
() -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_BRICKS = register("compacted_cloud_bricks", new CompactedCloudBlock(CLOUD_BRICKS.getDefaultState()));
Block CLOUD_BRICK_SLAB = register("cloud_brick_slab", new CloudSlabBlock(Settings.copy(CLOUD_BRICKS), false, null), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICK_STAIRS = register("cloud_brick_stairs", new CloudStairsBlock(CLOUD_BRICKS.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD = register("etched_cloud", new NaturalCloudBlock(Settings.copy(CLOUD_BRICKS), false,
null,
() -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_ETCHED_CLOUD = register("compacted_etched_cloud", new CompactedCloudBlock(ETCHED_CLOUD.getDefaultState()));
Block ETCHED_CLOUD_SLAB = register("etched_cloud_slab", new CloudSlabBlock(Settings.copy(ETCHED_CLOUD), false, null), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD_STAIRS = register("etched_cloud_stairs", new CloudStairsBlock(ETCHED_CLOUD.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(Settings.copy(CLOUD).hardness(0.7F), () -> UBlocks.CLOUD));
SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(Settings.copy(SOGGY_CLOUD), () -> UBlocks.CLOUD_SLAB));
SoggyCloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new SoggyCloudStairsBlock(SOGGY_CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.CLOUD_STAIRS));
Block DENSE_CLOUD = register("dense_cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid(), false,
null,
() -> UBlocks.COMPACTED_DENSE_CLOUD), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_DENSE_CLOUD = register("compacted_dense_cloud", new CompactedCloudBlock(DENSE_CLOUD.getDefaultState()));
Block DENSE_CLOUD_SLAB = register("dense_cloud_slab", new CloudSlabBlock(Settings.copy(DENSE_CLOUD), false, null), ItemGroups.BUILDING_BLOCKS);
Block DENSE_CLOUD_STAIRS = register("dense_cloud_stairs", new CloudStairsBlock(DENSE_CLOUD.getDefaultState(), Settings.copy(DENSE_CLOUD)), ItemGroups.BUILDING_BLOCKS);
Block CARVED_CLOUD = register("carved_cloud", new OrientedCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false), ItemGroups.BUILDING_BLOCKS);
Block UNSTABLE_CLOUD = register("unstable_cloud", new UnstableCloudBlock(Settings.copy(CLOUD)), ItemGroups.NATURAL); Block UNSTABLE_CLOUD = register("unstable_cloud", new UnstableCloudBlock(Settings.copy(CLOUD)), ItemGroups.NATURAL);
SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(Settings.copy(CLOUD), () -> UBlocks.CLOUD)); Block CLOUD_PILLAR = register("cloud_pillar", new CloudPillarBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid()), ItemGroups.NATURAL);
SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(Settings.copy(CLOUD), () -> UBlocks.CLOUD_SLAB)); Block CLOUD_CHEST = register("cloud_chest", new CloudChestBlock(Settings.copy(DENSE_CLOUD).instrument(Instrument.BASS).strength(2.5f), DENSE_CLOUD.getDefaultState()), ItemGroups.FUNCTIONAL);
CloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new CloudStairsBlock(SOGGY_CLOUD.getDefaultState(), Settings.copy(CLOUD), null)); Block CLOTH_BED = register("cloth_bed", new FancyBedBlock("cloth", Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOD)));
Block DENSE_CLOUD = register("dense_cloud", new CloudBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL), false), ItemGroups.NATURAL); Block CLOUD_BED = register("cloud_bed", new CloudBedBlock("cloud", CLOUD.getDefaultState(), Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOL)));
Block DENSE_CLOUD_SLAB = register("dense_cloud_slab", new CloudSlabBlock(Settings.copy(DENSE_CLOUD), false, null), ItemGroups.NATURAL);
Block DENSE_CLOUD_STAIRS = register("dense_cloud_stairs", new CloudStairsBlock(DENSE_CLOUD.getDefaultState(), Settings.copy(DENSE_CLOUD), null), ItemGroups.NATURAL); Block CLAM_SHELL = register("clam_shell", new ShellsBlock(Settings.create().mapColor(MapColor.DULL_PINK).breakInstantly().nonOpaque()));
Block CLOUD_PILLAR = register("cloud_pillar", new CloudPillarBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.NATURAL); Block SCALLOP_SHELL = register("scallop_shell", new ShellsBlock(Settings.create().mapColor(MapColor.DULL_PINK).breakInstantly().nonOpaque()));
Block CLOUD_BED = register("cloud_bed", new CloudBedBlock(CLOUD.getDefaultState(), Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOL))); Block TURRET_SHELL = register("turret_shell", new ShellsBlock(Settings.create().mapColor(MapColor.DULL_PINK).breakInstantly().nonOpaque()));
Block STABLE_DOOR = register("stable_door", new StableDoorBlock(Settings.copy(Blocks.OAK_DOOR), BlockSetType.OAK), ItemGroups.FUNCTIONAL);
Block DARK_OAK_DOOR = register("dark_oak_stable_door", new StableDoorBlock(Settings.copy(Blocks.OAK_DOOR), BlockSetType.OAK), ItemGroups.FUNCTIONAL);
Block CRYSTAL_DOOR = register("crystal_door", new CrystalDoorBlock(Settings.copy(Blocks.IRON_DOOR), UWoodTypes.CRYSTAL), ItemGroups.FUNCTIONAL);
Block CLOUD_DOOR = register("cloud_door", new CloudDoorBlock(Settings.copy(CLOUD), CLOUD.getDefaultState(), UWoodTypes.CLOUD), ItemGroups.FUNCTIONAL);
private static <T extends Block> T register(String name, T item) { private static <T extends Block> T register(String name, T item) {
return register(Unicopia.id(name), item); return register(Unicopia.id(name), item);
@ -172,9 +215,7 @@ public interface UBlocks {
} }
static <T extends Block> T register(Identifier id, T block, RegistryKey<ItemGroup> group) { static <T extends Block> T register(Identifier id, T block, RegistryKey<ItemGroup> group) {
ItemGroupRegistry.register(id, ItemGroupRegistry.register(id, block instanceof CloudLike ? new CloudBlockItem(block, new Item.Settings()) : new BlockItem(block, new Item.Settings()), group);
CloudBlock.isCloudBlock(block) ? new CloudBlockItem(block, new Item.Settings()) : new BlockItem(block, new Item.Settings()
), group);
return register(id, block); return register(id, block);
} }
@ -185,7 +226,7 @@ public interface UBlocks {
if (block instanceof SaplingBlock || block instanceof SproutBlock || block instanceof FruitBlock || block instanceof CropBlock || block instanceof DoorBlock || block instanceof TrapdoorBlock) { if (block instanceof SaplingBlock || block instanceof SproutBlock || block instanceof FruitBlock || block instanceof CropBlock || block instanceof DoorBlock || block instanceof TrapdoorBlock) {
TRANSLUCENT_BLOCKS.add(block); TRANSLUCENT_BLOCKS.add(block);
} }
if (CloudBlock.isCloudBlock(block) || block instanceof SlimePustuleBlock || block instanceof PileBlock) { if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) {
SEMI_TRANSPARENT_BLOCKS.add(block); SEMI_TRANSPARENT_BLOCKS.add(block);
} }
return Registry.register(Registries.BLOCK, id, block); return Registry.register(Registries.BLOCK, id, block);
@ -199,7 +240,7 @@ public interface UBlocks {
StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG); StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG);
StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD); StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD);
StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_WOOD); StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_WOOD);
Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES); Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL);
TintedBlock.REGISTRY.add(PALM_LEAVES); TintedBlock.REGISTRY.add(PALM_LEAVES);
FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60);

View file

@ -1,25 +1,34 @@
package com.minelittlepony.unicopia.block; package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.terraformersmc.terraform.boat.api.TerraformBoatType; import com.terraformersmc.terraform.boat.api.TerraformBoatType;
import com.terraformersmc.terraform.boat.api.TerraformBoatTypeRegistry; import com.terraformersmc.terraform.boat.api.TerraformBoatTypeRegistry;
import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeBuilder;
import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeBuilder;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.WoodType; import net.minecraft.block.WoodType;
import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKey;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public interface UWoodTypes { public interface UWoodTypes {
WoodType PALM = register("palm"); WoodType PALM = register("palm");
RegistryKey<TerraformBoatType> PALM_BOAT_TYPE = TerraformBoatTypeRegistry.createKey(Unicopia.id("palm")); RegistryKey<TerraformBoatType> PALM_BOAT_TYPE = TerraformBoatTypeRegistry.createKey(Unicopia.id("palm"));
BlockSetType CLOUD = new BlockSetTypeBuilder()
.soundGroup(BlockSoundGroup.WOOL)
.doorCloseSound(USounds.Vanilla.BLOCK_WOOL_HIT)
.doorOpenSound(USounds.Vanilla.BLOCK_WOOL_BREAK)
.register(Unicopia.id("cloud"));
BlockSetType CRYSTAL = BlockSetTypeBuilder.copyOf(BlockSetType.IRON)
.soundGroup(BlockSoundGroup.AMETHYST_BLOCK)
.openableByHand(true)
.register(Unicopia.id("crystal"));
static WoodType register(String name) { static WoodType register(String name) {
Identifier id = Unicopia.id(name); Identifier id = Unicopia.id(name);
return new WoodTypeBuilder().register(id, new BlockSetTypeBuilder().register(id)); return new WoodTypeBuilder().register(id, new BlockSetTypeBuilder().register(id));
} }
} }

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore; import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.*; import net.minecraft.state.property.*;
@ -13,12 +14,13 @@ public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
public static final EnumProperty<ZapAppleStageStore.Stage> STAGE = EnumProperty.of("stage", ZapAppleStageStore.Stage.class); public static final EnumProperty<ZapAppleStageStore.Stage> STAGE = EnumProperty.of("stage", ZapAppleStageStore.Stage.class);
ZapAppleLeavesBlock() { ZapAppleLeavesBlock() {
setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.HIBERNATING)); setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.GREENING));
} }
@Override @Override
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) { public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
if (oldState.isOf(state.getBlock()) if (state.get(PERSISTENT)
|| oldState.isOf(state.getBlock())
|| oldState.isOf(UBlocks.ZAP_LEAVES) || oldState.isOf(UBlocks.ZAP_LEAVES)
|| oldState.isOf(UBlocks.FLOWERING_ZAP_LEAVES) || oldState.isOf(UBlocks.FLOWERING_ZAP_LEAVES)
|| oldState.isOf(UBlocks.ZAP_LEAVES_PLACEHOLDER) || oldState.isOf(UBlocks.ZAP_LEAVES_PLACEHOLDER)
@ -33,6 +35,11 @@ public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
} }
} }
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING);
}
@Override @Override
protected ZapAppleStageStore.Stage getStage(BlockState state) { protected ZapAppleStageStore.Stage getStage(BlockState state) {
return state.get(STAGE); return state.get(STAGE);

View file

@ -3,20 +3,14 @@ package com.minelittlepony.unicopia.block.cloud;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.UBlockEntities; import com.minelittlepony.unicopia.block.FancyBedBlock;
import net.minecraft.block.BedBlock;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BedBlockEntity;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.ai.pathing.NavigationType; import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.DyeColor;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@ -25,12 +19,12 @@ import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudBedBlock extends BedBlock { public class CloudBedBlock extends FancyBedBlock implements CloudLike {
private final BlockState baseState; private final BlockState baseState;
private final CloudBlock baseBlock; private final CloudBlock baseBlock;
public CloudBedBlock(BlockState baseState, Settings settings) { public CloudBedBlock(String base, BlockState baseState, Settings settings) {
super(DyeColor.WHITE, settings); super(base, settings);
this.baseState = baseState; this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock(); this.baseBlock = (CloudBlock)baseState.getBlock();
} }
@ -83,20 +77,4 @@ public class CloudBedBlock extends BedBlock {
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) { public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) {
return true; return true;
} }
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new Tile(pos, state);
}
public static class Tile extends BedBlockEntity {
public Tile(BlockPos pos, BlockState state) {
super(pos, state);
}
@Override
public BlockEntityType<?> getType() {
return UBlockEntities.CLOUD_BED;
}
}
} }

View file

@ -26,11 +26,7 @@ import net.minecraft.world.EmptyBlockView;
import net.minecraft.world.LightType; import net.minecraft.world.LightType;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudBlock extends Block { public class CloudBlock extends Block implements CloudLike {
public static boolean isCloudBlock(Block block) {
return block instanceof CloudBlock || block instanceof CloudStairsBlock || block instanceof CloudBedBlock;
}
protected final boolean meltable; protected final boolean meltable;
public CloudBlock(Settings settings, boolean meltable) { public CloudBlock(Settings settings, boolean meltable) {
@ -59,7 +55,7 @@ public class CloudBlock extends Block {
entity.handleFallDamage(fallDistance, 0, world.getDamageSources().fall()); entity.handleFallDamage(fallDistance, 0, world.getDamageSources().fall());
generateSurfaceParticles(world, state, pos, ShapeContext.absent(), 9); generateSurfaceParticles(world, state, pos, ShapeContext.absent(), 9);
if (fallDistance > 7) { if (!world.isClient && fallDistance > 7) {
world.breakBlock(pos, true); world.breakBlock(pos, true);
} }
} }

View file

@ -0,0 +1,159 @@
package com.minelittlepony.unicopia.block.cloud;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.UBlockEntities;
import net.minecraft.block.BlockState;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.DoubleBlockProperties;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.DoubleInventory;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class CloudChestBlock extends ChestBlock implements CloudLike {
private final BlockState baseState;
private final CloudBlock baseBlock;
private static final DoubleBlockProperties.PropertyRetriever<ChestBlockEntity, Optional<NamedScreenHandlerFactory>> NAME_RETRIEVER = new DoubleBlockProperties.PropertyRetriever<>(){
@Override
public Optional<NamedScreenHandlerFactory> getFromBoth(final ChestBlockEntity first, final ChestBlockEntity second) {
final DoubleInventory inventory = new DoubleInventory(first, second);
return Optional.of(new NamedScreenHandlerFactory(){
@Override
@Nullable
public ScreenHandler createMenu(int i, PlayerInventory playerInventory, PlayerEntity player) {
if (first.checkUnlocked(player) && second.checkUnlocked(player)) {
first.checkLootInteraction(playerInventory.player);
second.checkLootInteraction(playerInventory.player);
return GenericContainerScreenHandler.createGeneric9x6(i, playerInventory, inventory);
}
return null;
}
@Override
public Text getDisplayName() {
if (first.hasCustomName()) {
return first.getDisplayName();
}
if (second.hasCustomName()) {
return second.getDisplayName();
}
return Text.translatable(first.getCachedState().getBlock().getTranslationKey() + ".double");
}
});
}
@Override
public Optional<NamedScreenHandlerFactory> getFrom(ChestBlockEntity chest) {
return Optional.of(chest);
}
@Override
public Optional<NamedScreenHandlerFactory> getFallback() {
return Optional.empty();
}
};
public CloudChestBlock(Settings settings, BlockState baseState) {
super(settings, () -> UBlockEntities.CLOUD_CHEST);
this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock();
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new TileData(pos, state);
}
@Override
@Nullable
public NamedScreenHandlerFactory createScreenHandlerFactory(BlockState state, World world, BlockPos pos) {
return getBlockEntitySource(state, world, pos, false).apply(NAME_RETRIEVER).orElse(null);
}
@Override
public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
if (!baseBlock.canInteract(baseState, world, pos, EquineContext.of(context))) {
return VoxelShapes.empty();
}
return super.getOutlineShape(state, world, pos, context);
}
@Override
@Deprecated
public final VoxelShape getCullingShape(BlockState state, BlockView world, BlockPos pos) {
return super.getOutlineShape(state, world, pos, ShapeContext.absent());
}
@Override
@Deprecated
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.collidable ? state.getOutlineShape(world, pos, context) : VoxelShapes.empty();
}
@Override
@Nullable
public final BlockState getPlacementState(ItemPlacementContext context) {
if (!baseBlock.canInteract(baseState, context.getWorld(), context.getBlockPos(), EquineContext.of(context))) {
return null;
}
return super.getPlacementState(context);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!baseBlock.canInteract(baseState, world, pos, EquineContext.of(player))) {
return ActionResult.PASS;
}
return super.onUse(state, world, pos, player, hand, hit);
}
@Deprecated
@Override
public void onEntityCollision(BlockState state, World world, BlockPos pos, Entity entity) {
baseState.onEntityCollision(world, pos, entity);
}
@Override
@Deprecated
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) {
return true;
}
public static class TileData extends ChestBlockEntity {
protected TileData(BlockEntityType<?> blockEntityType, BlockPos blockPos, BlockState blockState) {
super(blockEntityType, blockPos, blockState);
}
public TileData(BlockPos pos, BlockState state) {
super(UBlockEntities.CLOUD_CHEST, pos, state);
}
@Override
protected Text getContainerName() {
return getCachedState().getBlock().getName();
}
}
}

View file

@ -0,0 +1,87 @@
package com.minelittlepony.unicopia.block.cloud;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.Race;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
import net.minecraft.block.DoorBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class CloudDoorBlock extends DoorBlock implements CloudLike {
private final BlockState baseState;
private final CloudBlock baseBlock;
public CloudDoorBlock(Settings settings, BlockState baseState, BlockSetType blockSet) {
super(settings, blockSet);
this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock();
}
@Override
public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
if (canPassThrough(state, world, pos, EquineContext.of(context))) {
return VoxelShapes.empty();
}
return super.getOutlineShape(state, world, pos, context);
}
protected boolean canPassThrough(BlockState state, BlockView world, BlockPos pos, EquineContext context) {
return context.getCompositeRace().any(Race::canUseEarth);
}
@Override
@Deprecated
public final VoxelShape getCullingShape(BlockState state, BlockView world, BlockPos pos) {
return super.getOutlineShape(state, world, pos, ShapeContext.absent());
}
@Override
@Deprecated
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.collidable ? state.getOutlineShape(world, pos, context) : VoxelShapes.empty();
}
@Override
@Nullable
public final BlockState getPlacementState(ItemPlacementContext context) {
if (!baseBlock.canInteract(baseState, context.getWorld(), context.getBlockPos(), EquineContext.of(context))) {
return null;
}
return super.getPlacementState(context);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!baseBlock.canInteract(baseState, world, pos, EquineContext.of(player))) {
return ActionResult.PASS;
}
return super.onUse(state, world, pos, player, hand, hit);
}
@Deprecated
@Override
public void onEntityCollision(BlockState state, World world, BlockPos pos, Entity entity) {
baseState.onEntityCollision(world, pos, entity);
EquineContext context = EquineContext.of(entity);
if (!baseBlock.canInteract(baseState, world, pos, context)) {
entity.setVelocity(entity.getVelocity().multiply(0.5F, 1, 0.5F));
}
}
}

View file

@ -0,0 +1,5 @@
package com.minelittlepony.unicopia.block.cloud;
public interface CloudLike {
}

View file

@ -1,7 +1,5 @@
package com.minelittlepony.unicopia.block.cloud; package com.minelittlepony.unicopia.block.cloud;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
@ -18,15 +16,13 @@ import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudStairsBlock extends StairsBlock implements Soakable { public class CloudStairsBlock extends StairsBlock implements CloudLike {
private final CloudBlock baseBlock; private final CloudBlock baseBlock;
private final @Nullable Supplier<Soakable> soggyBlock;
public CloudStairsBlock(BlockState baseState, Settings settings, @Nullable Supplier<Soakable> soggyBlock) { public CloudStairsBlock(BlockState baseState, Settings settings) {
super(baseState, settings); super(baseState, settings);
this.baseBlock = (CloudBlock)baseState.getBlock(); this.baseBlock = (CloudBlock)baseState.getBlock();
this.soggyBlock = soggyBlock;
} }
@Override @Override
@ -92,15 +88,4 @@ public class CloudStairsBlock extends StairsBlock implements Soakable {
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) { public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) {
return true; return true;
} }
@Nullable
@Override
public BlockState getSoggyState(int moisture) {
return soggyBlock == null ? (baseBlock instanceof Soakable s ? s.getSoggyState(moisture) : null) : soggyBlock.get().getSoggyState(moisture);
}
@Override
public int getMoisture(BlockState state) {
return baseBlock instanceof Soakable s ? s.getMoisture(state) : 0;
}
} }

View file

@ -19,6 +19,8 @@ import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.Property; import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.BlockMirror;
import net.minecraft.util.BlockRotation;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.Util; import net.minecraft.util.Util;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
@ -43,13 +45,21 @@ public class CompactedCloudBlock extends CloudBlock {
); );
}); });
public CompactedCloudBlock(Settings settings) { private final BlockState baseState;
super(settings, true);
public CompactedCloudBlock(BlockState baseState) {
super(Settings.copy(baseState.getBlock()).dropsLike(baseState.getBlock()), true);
this.baseState = baseState;
PROPERTIES.forEach(property -> { PROPERTIES.forEach(property -> {
setDefaultState(getDefaultState().with(property, true)); setDefaultState(getDefaultState().with(property, true));
}); });
} }
@Override
public ItemStack getPickStack(BlockView world, BlockPos pos, BlockState state) {
return baseState.getBlock().getPickStack(world, pos, baseState);
}
@Override @Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context, EquineContext equineContext) { protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context, EquineContext equineContext) {
return SHAPE_CACHE.apply(state); return SHAPE_CACHE.apply(state);
@ -76,4 +86,24 @@ public class CompactedCloudBlock extends CloudBlock {
return ActionResult.PASS; return ActionResult.PASS;
} }
@Override
public BlockState rotate(BlockState state, BlockRotation rotation) {
return transform(state, rotation::rotate);
}
@Override
public BlockState mirror(BlockState state, BlockMirror mirror) {
return transform(state, mirror::apply);
}
private BlockState transform(BlockState state, Function<Direction, Direction> transformation) {
BlockState result = state;
for (var property : FACING_PROPERTIES.entrySet()) {
if (property.getKey().getAxis() != Direction.Axis.Y) {
result = result.with(FACING_PROPERTIES.get(transformation.apply(property.getKey())), state.get(property.getValue()));
}
}
return result;
}
} }

View file

@ -0,0 +1,42 @@
package com.minelittlepony.unicopia.block.cloud;
import com.minelittlepony.unicopia.EquineContext;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.DirectionProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.util.BlockMirror;
import net.minecraft.util.BlockRotation;
import net.minecraft.util.math.Direction;
public class OrientedCloudBlock extends CloudBlock {
public static final DirectionProperty FACING = Properties.FACING;
public OrientedCloudBlock(Settings settings, boolean meltable) {
super(settings, meltable);
this.setDefaultState(getDefaultState().with(FACING, Direction.UP));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(FACING);
}
@Override
public BlockState rotate(BlockState state, BlockRotation rotation) {
return state.with(FACING, rotation.rotate(state.get(FACING)));
}
@Override
public BlockState mirror(BlockState state, BlockMirror mirror) {
return state.rotate(mirror.getRotation(state.get(FACING)));
}
@Override
public BlockState getPlacementState(ItemPlacementContext ctx, EquineContext equineContext) {
return getDefaultState().with(FACING, ctx.getSide().getOpposite());
}
}

View file

@ -20,20 +20,22 @@ public class PoreousCloudBlock extends CloudBlock implements Soakable {
@Nullable @Nullable
@Override @Override
public BlockState getSoggyState(int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
return soggyBlock == null ? null : soggyBlock.get().getSoggyState(moisture); if (moisture <= 0) {
} return Soakable.copyProperties(state, getDefaultState());
}
@Override return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture);
public int getMoisture(BlockState state) {
return 0;
} }
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (soggyBlock != null && world.hasRain(pos) && world.isAir(pos.up())) { if (state.getBlock() instanceof Soakable soakable && world.hasRain(pos) && world.isAir(pos.up())) {
world.setBlockState(pos, Soakable.copyProperties(state, soggyBlock.get().getSoggyState(random.nextBetween(1, 5)))); @Nullable
return; BlockState soggyState = soakable.getStateWithMoisture(state, random.nextBetween(1, 5));
if (soggyState != null) {
world.setBlockState(pos, soggyState);
return;
}
} }
super.randomTick(state, world, pos, random); super.randomTick(state, world, pos, random);

View file

@ -0,0 +1,26 @@
package com.minelittlepony.unicopia.block.cloud;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakable {
protected final Supplier<Soakable> soggyBlock;
public PoreousCloudStairsBlock(BlockState baseState, Settings settings, Supplier<Soakable> soggyBlock) {
super(baseState, settings);
this.soggyBlock = soggyBlock;
}
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState());
}
return soggyBlock.get().getStateWithMoisture(state, moisture);
}
}

View file

@ -0,0 +1,62 @@
package com.minelittlepony.unicopia.block.cloud;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.container.ShapingBenchScreenHandler;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.screen.SimpleNamedScreenHandlerFactory;
import net.minecraft.stat.Stats;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class ShapingBenchBlock extends CloudBlock {
private static final VoxelShape SHAPE = VoxelShapes.union(
Block.createCuboidShape(0, 13, 0, 3, 18, 3),
Block.createCuboidShape(13, 13, 0, 16, 18, 3),
Block.createCuboidShape(0, 13, 13, 3, 18, 16),
Block.createCuboidShape(13, 13, 13, 16, 18, 16),
Block.createCuboidShape(0, 13, 0, 16, 16, 16),
Block.createCuboidShape(2, 0, 2, 14, 17, 14),
Block.createCuboidShape(0, 0, 0, 16, 4, 16)
);
public ShapingBenchBlock(Settings settings) {
super(settings, false);
}
@Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context, EquineContext equineContext) {
return SHAPE;
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (world.isClient) {
return ActionResult.SUCCESS;
}
player.openHandledScreen(state.createScreenHandlerFactory(world, pos));
player.incrementStat(Stats.INTERACT_WITH_STONECUTTER);
return ActionResult.CONSUME;
}
@Override
@Nullable
public NamedScreenHandlerFactory createScreenHandlerFactory(BlockState state, World world, BlockPos pos) {
return new SimpleNamedScreenHandlerFactory((syncId, playerInventory, player) -> {
return new ShapingBenchScreenHandler(syncId, playerInventory, ScreenHandlerContext.create(world, pos));
}, getName());
}
}

View file

@ -6,7 +6,6 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -31,9 +30,12 @@ public interface Soakable {
IntProperty MOISTURE = IntProperty.of("moisture", 1, 7); IntProperty MOISTURE = IntProperty.of("moisture", 1, 7);
Direction[] DIRECTIONS = Arrays.stream(Direction.values()).filter(d -> d != Direction.UP).toArray(Direction[]::new); Direction[] DIRECTIONS = Arrays.stream(Direction.values()).filter(d -> d != Direction.UP).toArray(Direction[]::new);
BlockState getSoggyState(int moisture); @Nullable
BlockState getStateWithMoisture(BlockState state, int moisture);
int getMoisture(BlockState state); default int getMoisture(BlockState state) {
return state.getOrEmpty(MOISTURE).orElse(0);
}
static void addMoistureParticles(BlockState state, World world, BlockPos pos, Random random) { static void addMoistureParticles(BlockState state, World world, BlockPos pos, Random random) {
if (random.nextInt(5) == 0) { if (random.nextInt(5) == 0) {
@ -46,69 +48,71 @@ public interface Soakable {
} }
} }
static ActionResult tryCollectMoisture(Block dryBlock, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { static ActionResult tryCollectMoisture(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ItemStack stack = player.getStackInHand(hand); if (state.getBlock() instanceof Soakable soakable) {
if (stack.getItem() == Items.GLASS_BOTTLE) { ItemStack stack = player.getStackInHand(hand);
if (!player.isCreative()) { if (stack.getItem() == Items.GLASS_BOTTLE) {
stack.split(1); if (!player.isCreative()) {
} stack.split(1);
if (stack.isEmpty()) { }
player.setStackInHand(hand, Items.POTION.getDefaultStack()); if (stack.isEmpty()) {
} else { player.setStackInHand(hand, Items.POTION.getDefaultStack());
player.giveItemStack(Items.POTION.getDefaultStack()); } else {
} player.giveItemStack(Items.POTION.getDefaultStack());
world.playSound(player, player.getX(), player.getY(), player.getZ(), USounds.Vanilla.ITEM_BOTTLE_FILL, SoundCategory.NEUTRAL, 1, 1); }
world.emitGameEvent(player, GameEvent.FLUID_PICKUP, pos); world.playSound(player, player.getX(), player.getY(), player.getZ(), USounds.Vanilla.ITEM_BOTTLE_FILL, SoundCategory.NEUTRAL, 1, 1);
updateMoisture(dryBlock, state, world, pos, state.get(MOISTURE) - 1); world.emitGameEvent(player, GameEvent.FLUID_PICKUP, pos);
updateMoisture(soakable, state, world, pos, soakable.getMoisture(state) - 1);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
}
} }
return ActionResult.PASS; return ActionResult.PASS;
} }
static void tickMoisture(Block dryBlock, BlockState state, ServerWorld world, BlockPos pos, Random random) { static void tickMoisture(BlockState state, ServerWorld world, BlockPos pos, Random random) {
int moisture = state.get(MOISTURE); if (state.getBlock() instanceof Soakable soakable) {
int moisture = soakable.getMoisture(state);
if (world.hasRain(pos) && world.isAir(pos.up())) { if (world.hasRain(pos) && world.isAir(pos.up())) {
if (moisture < 7) { if (moisture < 7) {
world.setBlockState(pos, state.with(MOISTURE, moisture + 1)); world.setBlockState(pos, soakable.getStateWithMoisture(state, moisture + 1));
} }
} else { } else {
if (moisture > 1) { if (moisture > 1) {
BlockPos neighborPos = pos.offset(Util.getRandom(Soakable.DIRECTIONS, random)); BlockPos neighborPos = pos.offset(Util.getRandom(Soakable.DIRECTIONS, random));
BlockState neighborState = world.getBlockState(neighborPos); BlockState neighborState = world.getBlockState(neighborPos);
if (neighborState.getBlock() instanceof Soakable soakable && soakable.getMoisture(neighborState) < moisture) { if (neighborState.getBlock() instanceof Soakable neighborSoakable && neighborSoakable.getMoisture(neighborState) < moisture) {
int half = Math.max(1, moisture / 2); int half = Math.max(1, moisture / 2);
@Nullable @Nullable
BlockState newNeighborState = soakable.getSoggyState(half); BlockState newNeighborState = neighborSoakable.getStateWithMoisture(neighborState, half);
if (newNeighborState != null) { if (newNeighborState != null) {
updateMoisture(dryBlock, state, world, pos, moisture - half); updateMoisture(soakable, state, world, pos, moisture - half);
world.setBlockState(neighborPos, soakable.getSoggyState(half)); world.setBlockState(neighborPos, soakable.getStateWithMoisture(state, half));
world.emitGameEvent(null, GameEvent.BLOCK_CHANGE, neighborPos); world.emitGameEvent(null, GameEvent.BLOCK_CHANGE, neighborPos);
return; return;
}
} }
} }
updateMoisture(soakable, state, world, pos, moisture - 1);
} }
updateMoisture(dryBlock, state, world, pos, moisture - 1);
} }
} }
private static void updateMoisture(Block dryBlock, BlockState state, World world, BlockPos pos, int newMoisture) { private static void updateMoisture(Soakable soakable, BlockState state, World world, BlockPos pos, int newMoisture) {
if (newMoisture <= 0) { world.setBlockState(pos, soakable.getStateWithMoisture(state, newMoisture));
world.setBlockState(pos, copyProperties(state, dryBlock.getDefaultState()));
} else {
world.setBlockState(pos, state.with(MOISTURE, newMoisture));
}
world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F)); world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F));
} }
@Nullable
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
static BlockState copyProperties(BlockState from, BlockState to) { static BlockState copyProperties(BlockState from, @Nullable BlockState to) {
for (Property property : from.getProperties()) { if (to != null) {
to = to.withIfExists(property, from.get(property)); for (Property property : from.getProperties()) {
to = to.withIfExists(property, from.get(property));
}
} }
return to; return to;
} }

View file

@ -2,9 +2,12 @@ package com.minelittlepony.unicopia.block.cloud;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
@ -12,6 +15,7 @@ import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class SoggyCloudBlock extends CloudBlock implements Soakable { public class SoggyCloudBlock extends CloudBlock implements Soakable {
@ -24,25 +28,30 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
this.dryBlock = dryBlock; this.dryBlock = dryBlock;
} }
@Override
public BlockState getSoggyState(int moisture) {
return getDefaultState().with(MOISTURE, moisture);
}
@Override
public int getMoisture(BlockState state) {
return state.get(MOISTURE);
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder);
builder.add(MOISTURE); builder.add(MOISTURE);
} }
@Override
public ItemStack getPickStack(BlockView world, BlockPos pos, BlockState state) {
return dryBlock.get().getPickStack(world, pos, state);
}
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
return Soakable.copyProperties(state, dryBlock.get().getDefaultState());
}
return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture);
}
@Override @Override
@Deprecated @Deprecated
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
return Soakable.tryCollectMoisture(dryBlock.get(), state, world, pos, player, hand, hit); return Soakable.tryCollectMoisture(state, world, pos, player, hand, hit);
} }
@Override @Override
@ -53,6 +62,6 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
Soakable.tickMoisture(dryBlock.get(), state, world, pos, random); Soakable.tickMoisture(state, world, pos, random);
} }
} }

View file

@ -2,9 +2,12 @@ package com.minelittlepony.unicopia.block.cloud;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
@ -12,6 +15,7 @@ import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class SoggyCloudSlabBlock extends CloudSlabBlock { public class SoggyCloudSlabBlock extends CloudSlabBlock {
@ -24,26 +28,30 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
this.dryBlock = dryBlock; this.dryBlock = dryBlock;
} }
@Override
public BlockState getSoggyState(int moisture) {
return getDefaultState().with(MOISTURE, moisture);
}
@Override
public int getMoisture(BlockState state) {
return state.get(MOISTURE);
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder); super.appendProperties(builder);
builder.add(MOISTURE); builder.add(MOISTURE);
} }
@Override
public ItemStack getPickStack(BlockView world, BlockPos pos, BlockState state) {
return dryBlock.get().getPickStack(world, pos, state);
}
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
return Soakable.copyProperties(state, dryBlock.get().getDefaultState());
}
return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture);
}
@Override @Override
@Deprecated @Deprecated
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
return Soakable.tryCollectMoisture(dryBlock.get(), state, world, pos, player, hand, hit); return Soakable.tryCollectMoisture(state, world, pos, player, hand, hit);
} }
@Override @Override
@ -54,6 +62,6 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
Soakable.tickMoisture(dryBlock.get(), state, world, pos, random); Soakable.tickMoisture(state, world, pos, random);
} }
} }

View file

@ -0,0 +1,43 @@
package com.minelittlepony.unicopia.block.cloud;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable {
private final Supplier<Block> dryBlock;
public SoggyCloudStairsBlock(BlockState baseState, Settings settings, Supplier<Block> dryBlock) {
super(baseState, settings);
setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder);
builder.add(MOISTURE);
}
@Override
public ItemStack getPickStack(BlockView world, BlockPos pos, BlockState state) {
return dryBlock.get().getPickStack(world, pos, state);
}
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
return Soakable.copyProperties(state, dryBlock.get().getDefaultState());
}
return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture);
}
}

View file

@ -59,6 +59,10 @@ public class UnstableCloudBlock extends CloudBlock {
} }
world.playSound(null, pos, SoundEvents.BLOCK_WOOL_HIT, SoundCategory.BLOCKS, 1, 1); world.playSound(null, pos, SoundEvents.BLOCK_WOOL_HIT, SoundCategory.BLOCKS, 1, 1);
if (world.isClient) {
return;
}
if (fallDistance > 3) { if (fallDistance > 3) {
world.breakBlock(pos, true); world.breakBlock(pos, true);
return; return;

View file

@ -1,7 +1,12 @@
package com.minelittlepony.unicopia.client; package com.minelittlepony.unicopia.client;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.block.*; import com.minelittlepony.unicopia.block.*;
import com.minelittlepony.unicopia.block.cloud.CloudChestBlock;
import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle; import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle;
import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle; import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle;
import com.minelittlepony.unicopia.client.particle.DiskParticle; import com.minelittlepony.unicopia.client.particle.DiskParticle;
@ -21,8 +26,8 @@ import com.minelittlepony.unicopia.client.render.spell.SpellRendererFactory;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.item.ChameleonItem; import com.minelittlepony.unicopia.item.ChameleonItem;
import com.minelittlepony.unicopia.item.EnchantableItem; import com.minelittlepony.unicopia.item.EnchantableItem;
import com.minelittlepony.unicopia.item.FancyBedItem;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBedItem;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.terraformersmc.terraform.boat.api.client.TerraformBoatClientHelper; import com.terraformersmc.terraform.boat.api.client.TerraformBoatClientHelper;
@ -30,9 +35,11 @@ import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry;
import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry.PendingParticleFactory; import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry.PendingParticleFactory;
import net.fabricmc.fabric.api.client.rendering.v1.*; import net.fabricmc.fabric.api.client.rendering.v1.*;
import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry.DynamicItemRenderer;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColorProvider;
import net.minecraft.client.color.world.BiomeColors; import net.minecraft.client.color.world.BiomeColors;
import net.minecraft.client.color.world.FoliageColors; import net.minecraft.client.color.world.FoliageColors;
import net.minecraft.client.item.ModelPredicateProviderRegistry; import net.minecraft.client.item.ModelPredicateProviderRegistry;
@ -43,13 +50,18 @@ import net.minecraft.client.render.block.entity.BlockEntityRendererFactories;
import net.minecraft.client.render.entity.FlyingItemEntityRenderer; import net.minecraft.client.render.entity.FlyingItemEntityRenderer;
import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.fluid.Fluids; import net.minecraft.fluid.Fluids;
import net.minecraft.item.*; import net.minecraft.item.*;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
public interface URenderers { public interface URenderers {
BlockEntity CHEST_RENDER_ENTITY = new CloudChestBlock.TileData(BlockPos.ORIGIN, UBlocks.CLOUD_CHEST.getDefaultState());
static void bootstrap() { static void bootstrap() {
ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.CHANGELING_MAGIC, createFactory(ChangelingMagicParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.CHANGELING_MAGIC, createFactory(ChangelingMagicParticle::new));
@ -65,14 +77,11 @@ public interface URenderers {
ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.LIGHTNING_BOLT, LightningBoltParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.LIGHTNING_BOLT, LightningBoltParticle::new);
AccessoryFeatureRenderer.register(BraceletFeatureRenderer::new); AccessoryFeatureRenderer.register(
AccessoryFeatureRenderer.register(AmuletFeatureRenderer::new); BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new,
AccessoryFeatureRenderer.register(WingsFeatureRenderer::new); WingsFeatureRenderer::new, HornFeatureRenderer::new, IcarusWingsFeatureRenderer::new, BatWingsFeatureRenderer::new,
AccessoryFeatureRenderer.register(HornFeatureRenderer::new); HeldEntityFeatureRenderer::new
AccessoryFeatureRenderer.register(IcarusWingsFeatureRenderer::new); );
AccessoryFeatureRenderer.register(BatWingsFeatureRenderer::new);
AccessoryFeatureRenderer.register(GlassesFeatureRenderer::new);
AccessoryFeatureRenderer.register(HeldEntityFeatureRenderer::new);
EntityRendererRegistry.register(UEntities.THROWN_ITEM, FlyingItemEntityRenderer::new); EntityRendererRegistry.register(UEntities.THROWN_ITEM, FlyingItemEntityRenderer::new);
EntityRendererRegistry.register(UEntities.MUFFIN, FlyingItemEntityRenderer::new); EntityRendererRegistry.register(UEntities.MUFFIN, FlyingItemEntityRenderer::new);
@ -87,98 +96,24 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.STORM_CLOUD, StormCloudEntityRenderer::new); EntityRendererRegistry.register(UEntities.STORM_CLOUD, StormCloudEntityRenderer::new);
EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new); EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new);
EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new); EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new);
EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new); BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.CLOUD_BED, CloudBedBlockEntityRenderer::new); BlockEntityRendererFactories.register(UBlockEntities.FANCY_BED, CloudBedBlockEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.CLOUD_CHEST, CloudChestBlockEntityRenderer::new);
register(URenderers::renderJarItem, UItems.FILLED_JAR);
register(URenderers::renderBedItem, UItems.CLOTH_BED, UItems.CLOUD_BED);
register(URenderers::renderChestItem, UBlocks.CLOUD_CHEST.asItem());
PolearmRenderer.register(UItems.WOODEN_POLEARM, UItems.STONE_POLEARM, UItems.IRON_POLEARM, UItems.GOLDEN_POLEARM, UItems.DIAMOND_POLEARM, UItems.NETHERITE_POLEARM);
ModelPredicateProviderRegistry.register(UItems.GEMSTONE, new Identifier("affinity"), (stack, world, entity, seed) -> EnchantableItem.isEnchanted(stack) ? EnchantableItem.getSpellKey(stack).getAffinity().getAlignment() : 0);
ModelPredicateProviderRegistry.register(UItems.ROCK_CANDY, new Identifier("count"), (stack, world, entity, seed) -> stack.getCount() / (float)stack.getMaxCount());
ColorProviderRegistry.BLOCK.register(URenderers::getTintedBlockColor, TintedBlock.REGISTRY.stream().toArray(Block[]::new));
ColorProviderRegistry.ITEM.register((stack, i) -> getTintedBlockColor(Block.getBlockFromItem(stack.getItem()).getDefaultState(), null, null, i), TintedBlock.REGISTRY.stream().map(Block::asItem).filter(i -> i != Items.AIR).toArray(Item[]::new));
ColorProviderRegistry.ITEM.register((stack, i) -> i > 0 ? -1 : ((DyeableItem)stack.getItem()).getColor(stack), UItems.FRIENDSHIP_BRACELET); ColorProviderRegistry.ITEM.register((stack, i) -> i > 0 ? -1 : ((DyeableItem)stack.getItem()).getColor(stack), UItems.FRIENDSHIP_BRACELET);
BuiltinItemRendererRegistry.INSTANCE.register(UItems.FILLED_JAR, (stack, mode, matrices, vertices, light, overlay) -> { ColorProviderRegistry.ITEM.register((stack, i) -> i > 0 || !EnchantableItem.isEnchanted(stack) ? -1 : EnchantableItem.getSpellKey(stack).getColor(), UItems.GEMSTONE);
ColorProviderRegistry.ITEM.register((stack, i) -> i == 1 && EnchantableItem.isEnchanted(stack) ? EnchantableItem.getSpellKey(stack).getColor() : -1, UItems.MAGIC_STAFF);
ItemRenderer renderer = MinecraftClient.getInstance().getItemRenderer();
ChameleonItem item = (ChameleonItem)stack.getItem();
// Reset stuff done in the beforelands
matrices.pop();
if (mode == ModelTransformationMode.GUI) {
DiffuseLighting.disableGuiDepthLighting();
}
VertexConsumerProvider.Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers();
ClientWorld world = MinecraftClient.getInstance().world;
if (item.hasAppearance(stack)) {
matrices.push();
if (mode.isFirstPerson()) {
matrices.translate(0.05, 0.06, 0.06);
} else if (mode == ModelTransformationMode.HEAD) {
matrices.translate(0, 0.4, 0);
} else if (mode == ModelTransformationMode.GROUND
|| mode == ModelTransformationMode.THIRD_PERSON_LEFT_HAND || mode == ModelTransformationMode.THIRD_PERSON_RIGHT_HAND) {
matrices.translate(0, 0.06, 0);
}
// GUI, FIXED, NONE - translate(0, 0, 0)
//matrices.scale(0.5F, 0.5F, 0.5F);
float scale = 0.5F;
matrices.scale(scale, scale, scale);
ItemStack appearance = item.getAppearanceStack(stack);
renderer.renderItem(appearance, mode, light, overlay, matrices, immediate, world, 0);
matrices.pop();
}
renderer.renderItem(item.createAppearanceStack(stack, UItems.EMPTY_JAR), mode, light, OverlayTexture.DEFAULT_UV, matrices, vertices, world, 0);
if (mode == ModelTransformationMode.GUI) {
DiffuseLighting.enableGuiDepthLighting();
}
matrices.push();
});
BuiltinItemRendererRegistry.INSTANCE.register(UItems.CLOUD_BED, (stack, mode, matrices, vertices, light, overlay) -> {
MinecraftClient.getInstance().getBlockEntityRenderDispatcher().renderEntity(((CloudBedItem)stack.getItem()).getRenderEntity(), matrices, vertices, light, overlay);
});
PolearmRenderer.register(UItems.WOODEN_POLEARM);
PolearmRenderer.register(UItems.STONE_POLEARM);
PolearmRenderer.register(UItems.IRON_POLEARM);
PolearmRenderer.register(UItems.GOLDEN_POLEARM);
PolearmRenderer.register(UItems.DIAMOND_POLEARM);
PolearmRenderer.register(UItems.NETHERITE_POLEARM);
ModelPredicateProviderRegistry.register(UItems.GEMSTONE, new Identifier("affinity"), (stack, world, entity, seed) -> {
return EnchantableItem.isEnchanted(stack) ? EnchantableItem.getSpellKey(stack).getAffinity().getAlignment() : 0;
});
ModelPredicateProviderRegistry.register(UItems.ROCK_CANDY, new Identifier("count"), (stack, world, entity, seed) -> {
return stack.getCount() / (float)stack.getMaxCount();
});
ColorProviderRegistry.ITEM.register((stack, i) -> {
return i > 0 || !EnchantableItem.isEnchanted(stack) ? -1 : EnchantableItem.getSpellKey(stack).getColor();
}, UItems.GEMSTONE);
ColorProviderRegistry.ITEM.register((stack, i) -> {
if (i == 1 && EnchantableItem.isEnchanted(stack)) {
return EnchantableItem.getSpellKey(stack).getColor();
}
return -1;
}, UItems.MAGIC_STAFF);
BlockColorProvider tintedProvider = (state, view, pos, color) -> {
if (view == null || pos == null) {
color = FoliageColors.getDefaultColor();
} else {
color = BiomeColors.getFoliageColor(view, pos);
}
if (state.getBlock() instanceof TintedBlock block) {
return block.getTint(state, view, pos, color);
}
return color;
};
ColorProviderRegistry.BLOCK.register(tintedProvider, TintedBlock.REGISTRY.stream().toArray(Block[]::new));
ColorProviderRegistry.ITEM.register((stack, i) -> {
return tintedProvider.getColor(Block.getBlockFromItem(stack.getItem()).getDefaultState(), null, null, i);
}, TintedBlock.REGISTRY.stream().map(Block::asItem).filter(i -> i != Items.AIR).toArray(Item[]::new));
BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getCutout(), UBlocks.TRANSLUCENT_BLOCKS.stream().toArray(Block[]::new)); BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getCutout(), UBlocks.TRANSLUCENT_BLOCKS.stream().toArray(Block[]::new));
BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getTranslucent(), UBlocks.SEMI_TRANSPARENT_BLOCKS.stream().toArray(Block[]::new)); BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getTranslucent(), UBlocks.SEMI_TRANSPARENT_BLOCKS.stream().toArray(Block[]::new));
@ -190,6 +125,80 @@ public interface URenderers {
SpellRendererFactory.bootstrap(); SpellRendererFactory.bootstrap();
} }
private static void register(DynamicItemRenderer renderer, ItemConvertible...items) {
for (ItemConvertible item : items) {
BuiltinItemRendererRegistry.INSTANCE.register(item, renderer);
}
}
@SuppressWarnings("unchecked")
private static void renderBedItem(ItemStack stack, ModelTransformationMode mode, MatrixStack matrices, VertexConsumerProvider vertices, int light, int overlay) {
BlockEntity entity = ((Supplier<BlockEntity>)stack.getItem()).get();
((FancyBedBlock.Tile)entity).setPattern(FancyBedItem.getPattern(stack));
MinecraftClient.getInstance().getBlockEntityRenderDispatcher().renderEntity(entity, matrices, vertices, light, overlay);
}
private static void renderChestItem(ItemStack stack, ModelTransformationMode mode, MatrixStack matrices, VertexConsumerProvider vertices, int light, int overlay) {
MinecraftClient.getInstance().getBlockEntityRenderDispatcher().renderEntity(CHEST_RENDER_ENTITY, matrices, vertices, light, overlay);
}
private static void renderJarItem(ItemStack stack, ModelTransformationMode mode, MatrixStack matrices, VertexConsumerProvider vertices, int light, int overlay) {
ItemRenderer renderer = MinecraftClient.getInstance().getItemRenderer();
ChameleonItem item = (ChameleonItem)stack.getItem();
// Reset stuff done in the beforelands
matrices.pop();
if (mode == ModelTransformationMode.GUI) {
DiffuseLighting.disableGuiDepthLighting();
}
VertexConsumerProvider.Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers();
ClientWorld world = MinecraftClient.getInstance().world;
if (item.hasAppearance(stack)) {
matrices.push();
if (mode.isFirstPerson()) {
matrices.translate(0.05, 0.06, 0.06);
} else if (mode == ModelTransformationMode.HEAD) {
matrices.translate(0, 0.4, 0);
} else if (mode == ModelTransformationMode.GROUND
|| mode == ModelTransformationMode.THIRD_PERSON_LEFT_HAND || mode == ModelTransformationMode.THIRD_PERSON_RIGHT_HAND) {
matrices.translate(0, 0.06, 0);
}
// GUI, FIXED, NONE - translate(0, 0, 0)
//matrices.scale(0.5F, 0.5F, 0.5F);
float scale = 0.5F;
matrices.scale(scale, scale, scale);
ItemStack appearance = item.getAppearanceStack(stack);
renderer.renderItem(appearance, mode, light, overlay, matrices, immediate, world, 0);
matrices.pop();
}
renderer.renderItem(item.createAppearanceStack(stack, UItems.EMPTY_JAR), mode, light, OverlayTexture.DEFAULT_UV, matrices, vertices, world, 0);
if (mode == ModelTransformationMode.GUI) {
DiffuseLighting.enableGuiDepthLighting();
}
matrices.push();
}
private static int getTintedBlockColor(BlockState state, @Nullable BlockRenderView view, @Nullable BlockPos pos, int color) {
if (view == null || pos == null) {
color = FoliageColors.getDefaultColor();
} else {
color = BiomeColors.getFoliageColor(view, pos);
}
if (state.getBlock() instanceof TintedBlock block) {
return block.getTint(state, view, pos, color);
}
return color;
}
static <T extends ParticleEffect> PendingParticleFactory<T> createFactory(ParticleSupplier<T> supplier) { static <T extends ParticleEffect> PendingParticleFactory<T> createFactory(ParticleSupplier<T> supplier) {
return provider -> (effect, world, x, y, z, dx, dy, dz) -> supplier.get(effect, provider, world, x, y, z, dx, dy, dz); return provider -> (effect, world, x, y, z, dx, dy, dz) -> supplier.get(effect, provider, world, x, y, z, dx, dy, dz);
} }

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.gui.LanSettingsScreen; import com.minelittlepony.unicopia.client.gui.LanSettingsScreen;
import com.minelittlepony.unicopia.client.gui.ShapingBenchScreen;
import com.minelittlepony.unicopia.client.gui.UHud; import com.minelittlepony.unicopia.client.gui.UHud;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
@ -101,6 +102,7 @@ public class UnicopiaClient implements ClientModInitializer {
URenderers.bootstrap(); URenderers.bootstrap();
HandledScreens.register(UScreenHandlers.SPELL_BOOK, SpellbookScreen::new); HandledScreens.register(UScreenHandlers.SPELL_BOOK, SpellbookScreen::new);
HandledScreens.register(UScreenHandlers.SHAPING_BENCH, ShapingBenchScreen::new);
ClientTickEvents.END_CLIENT_TICK.register(this::onTick); ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
ClientTickEvents.END_WORLD_TICK.register(this::onWorldTick); ClientTickEvents.END_WORLD_TICK.register(this::onWorldTick);

View file

@ -0,0 +1,61 @@
package com.minelittlepony.unicopia.client.gui;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.entity.duck.EntityDuck;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import com.minelittlepony.unicopia.entity.effect.UEffects;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.registry.tag.TagKey;
public class HudEffects {
private static boolean addedHunger;
private static Set<TagKey<Fluid>> originalTags = null;
public static void tryApply(@Nullable PlayerEntity player, float tickDelta, boolean on) {
if (player != null) {
apply(Pony.of(player), tickDelta, on);
}
}
private static void apply(Pony pony, float tickDelta, boolean on) {
if (on) {
if (!pony.asEntity().hasStatusEffect(StatusEffects.HUNGER) && EffectUtils.getAmplifier(pony.asEntity(), UEffects.FOOD_POISONING) > 0) {
addedHunger = true;
pony.asEntity().addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 1, 1, false, false));
}
} else {
if (addedHunger) {
addedHunger = false;
pony.asEntity().removeStatusEffect(StatusEffects.HUNGER);
}
}
if (pony.getCompositeRace().includes(Race.SEAPONY)) {
Set<TagKey<Fluid>> fluidTags = ((EntityDuck)pony.asEntity()).getSubmergedFluidTags();
if (on) {
originalTags = new HashSet<>(fluidTags);
if (fluidTags.contains(FluidTags.WATER)) {
fluidTags.clear();
} else {
fluidTags.add(FluidTags.WATER);
}
} else if (originalTags != null) {
fluidTags.clear();
fluidTags.addAll(originalTags);
originalTags = null;
}
}
}
}

View file

@ -113,7 +113,7 @@ public class LanSettingsScreen extends GameGui {
WHITELIST_GRID_PACKER.start(); WHITELIST_GRID_PACKER.start();
for (Race race : Race.REGISTRY) { for (Race race : Race.REGISTRY) {
if (!race.isUnset()) { if (!race.isUnset() && race.availability().isGrantable()) {
Bounds bound = WHITELIST_GRID_PACKER.next(); Bounds bound = WHITELIST_GRID_PACKER.next();
Button button = content.addButton(new Toggle(LEFT + bound.left + 10, row + bound.top, whitelist.contains(race.getId().toString()))) Button button = content.addButton(new Toggle(LEFT + bound.left + 10, row + bound.top, whitelist.contains(race.getId().toString())))

View file

@ -13,7 +13,7 @@ import net.minecraft.client.util.math.MatrixStack;
class ManaRingSlot extends Slot { class ManaRingSlot extends Slot {
public ManaRingSlot(UHud uHud, AbilitySlot normalSlot, AbilitySlot backupSlot, int x, int y) { public ManaRingSlot(UHud uHud, AbilitySlot normalSlot, AbilitySlot backupSlot, int x, int y) {
super(uHud, normalSlot, backupSlot, x, y, 8, UHud.PRIMARY_SLOT_SIZE, 33, 43, 42); super(uHud, normalSlot, backupSlot, x, y, 8, UHud.PRIMARY_SLOT_SIZE, 33, 43, 30);
background(0, 5); background(0, 5);
foreground(0, 59); foreground(0, 59);
} }

View file

@ -0,0 +1,12 @@
package com.minelittlepony.unicopia.client.gui;
import net.minecraft.client.gui.screen.ingame.StonecutterScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.StonecutterScreenHandler;
import net.minecraft.text.Text;
public class ShapingBenchScreen extends StonecutterScreen {
public ShapingBenchScreen(StonecutterScreenHandler handler, PlayerInventory inventory, Text title) {
super(handler, inventory, title);
}
}

View file

@ -93,8 +93,9 @@ class Slot {
AbilityDispatcher.Stat stat = abilities.getStat(bSwap ? bSlot : aSlot); AbilityDispatcher.Stat stat = abilities.getStat(bSwap ? bSlot : aSlot);
int iconPosition = ((size - iconSize + slotPadding + 1) / 2);
int sz = iconSize - slotPadding; int sz = iconSize - slotPadding;
uHud.renderAbilityIcon(context, stat, slotPadding, slotPadding, sz, sz, sz, sz); uHud.renderAbilityIcon(context, stat, iconPosition, iconPosition, sz, sz, sz, sz);
float cooldown = stat.getFillProgress(); float cooldown = stat.getFillProgress();

View file

@ -13,13 +13,13 @@ public class TextBlock extends Label {
public TextBlock(int x, int y, int width) { public TextBlock(int x, int y, int width) {
super(x, y); super(x, y);
this.maxWidth = width; this.maxWidth = width;
this.render(null, x, y, width);
} }
@Override @Override
public Bounds getBounds() { public Bounds getBounds() {
Bounds bounds = super.getBounds(); Bounds bounds = super.getBounds();
bounds.height = getFont().wrapLines(getStyle().getText(), maxWidth).size() * getFont().fontHeight; bounds.height = getFont().wrapLines(getStyle().getText(), maxWidth).size() * getFont().fontHeight;
bounds.width = 0;
return bounds; return bounds;
} }

View file

@ -41,10 +41,10 @@ public class TribeButton extends Button {
MinecraftClient mc = MinecraftClient.getInstance(); MinecraftClient mc = MinecraftClient.getInstance();
context.drawTexture(TribeSelectionScreen.TEXTURE, getX() - 3, getY() - 13, 0, 0, 76, 69); context.drawTexture(TribeSelectionScreen.TEXTURE, getX() - 3, getY() - 13, 0, 0, 76, 69);
if (isHovered()) { if (isSelected()) {
context.drawTexture(TribeSelectionScreen.TEXTURE, getX() - 4, getY() - 14, 76, 0, 78, 71); context.drawTexture(TribeSelectionScreen.TEXTURE, getX() - 4, getY() - 14, 76, 0, 78, 71);
if (hovered && screenWidth > 0) { if (isFocused() && screenWidth > 0) {
Identifier id = Race.REGISTRY.getId(race); Identifier id = Race.REGISTRY.getId(race);
context.drawCenteredTextWithShadow(getFont(), Text.translatable("gui.unicopia.tribe_selection.describe." + id.getNamespace() + "." + id.getPath()), screenWidth / 2, getY() + height, 0xFFFFFFFF); context.drawCenteredTextWithShadow(getFont(), Text.translatable("gui.unicopia.tribe_selection.describe." + id.getNamespace() + "." + id.getPath()), screenWidth / 2, getY() + height, 0xFFFFFFFF);
} }
@ -59,7 +59,7 @@ public class TribeButton extends Button {
int foreColor = getStyle().getColor(); int foreColor = getStyle().getColor();
if (!active) { if (!active) {
foreColor = 10526880; foreColor = 10526880;
} else if (isHovered()) { } else if (isSelected()) {
foreColor = 16777120; foreColor = 16777120;
} }

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.client.gui;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import com.minelittlepony.common.client.gui.GameGui; import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.ScrollContainer;
import com.minelittlepony.common.client.gui.element.Button; import com.minelittlepony.common.client.gui.element.Button;
import com.minelittlepony.common.client.gui.element.Label; import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
@ -17,6 +18,8 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
private final BooleanConsumer callback; private final BooleanConsumer callback;
private final ScrollContainer textBody = new ScrollContainer();
public TribeConfirmationScreen(BooleanConsumer callback, Race selection) { public TribeConfirmationScreen(BooleanConsumer callback, Race selection) {
super(Text.translatable("gui.unicopia.tribe_selection")); super(Text.translatable("gui.unicopia.tribe_selection"));
this.callback = callback; this.callback = callback;
@ -25,12 +28,26 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
@Override @Override
protected void init() { protected void init() {
final int columnHeight = 167; final int columnHeight = 167;
final int columnWidth = 310; final int columnWidth = 310;
final int padding = 15; final int padding = 15;
int top = (height - columnHeight) / 2; int top = (height - columnHeight) / 2;
int left = (width - columnWidth) / 2 + 8;
int maxWidth = 295;
textBody.verticalScrollbar.layoutToEnd = true;
textBody.margin.top = top + 43;
textBody.margin.left = left;
textBody.margin.right = width - (left + maxWidth);
textBody.margin.bottom = height - (textBody.margin.top + 130);
textBody.getContentPadding().top = 10;
textBody.getContentPadding().left = 8;
textBody.getContentPadding().bottom = 100;
textBody.getContentPadding().right = 0;
textBody.init(this::buildTextBody);
getChildElements().add(textBody);
addDrawableChild(new Button(width / 2 + 5, top + columnHeight + padding, 100, 20)) addDrawableChild(new Button(width / 2 + 5, top + columnHeight + padding, 100, 20))
.onClick(b -> callback.accept(true)) .onClick(b -> callback.accept(true))
@ -42,38 +59,40 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
addDrawable(new Label(width / 2, top - 30).setCentered()).getStyle().setText(Text.translatable("gui.unicopia.tribe_selection.confirm", selection.getDisplayName().copy().formatted(Formatting.YELLOW))); addDrawable(new Label(width / 2, top - 30).setCentered()).getStyle().setText(Text.translatable("gui.unicopia.tribe_selection.confirm", selection.getDisplayName().copy().formatted(Formatting.YELLOW)));
addDrawable(new TribeButton((width - 70) / 2, top, 0, selection)); addDrawable(new TribeButton((width - 70) / 2, top, 0, selection));
}
top += 43; private void buildTextBody() {
int top = 0;
int left = (width - columnWidth) / 2 + padding; int left = 0;
Text race = selection.getAltDisplayName().copy().formatted(Formatting.YELLOW); Text race = selection.getAltDisplayName().copy().formatted(Formatting.YELLOW);
addDrawable(new Label(left - 3, top += 10)).getStyle().setText(Text.translatable("gui.unicopia.tribe_selection.confirm.goods", race).formatted(Formatting.YELLOW)); textBody.addButton(new Label(left - 3, top += 10)).getStyle().setText(Text.translatable("gui.unicopia.tribe_selection.confirm.goods", race).formatted(Formatting.YELLOW));
top += 15; top += 15;
int maxWidth = 280; int maxWidth = 270;
Identifier id = Race.REGISTRY.getId(selection); Identifier id = Race.REGISTRY.getId(selection);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 10; i++) {
String key = String.format("gui.unicopia.tribe_selection.confirm.goods.%d.%s.%s", i, id.getNamespace(), id.getPath()); String key = String.format("gui.unicopia.tribe_selection.confirm.goods.%d.%s.%s", i, id.getNamespace(), id.getPath());
if (Language.getInstance().hasTranslation(key)) { if (Language.getInstance().hasTranslation(key)) {
TextBlock block = addDrawable(new TextBlock(left, top, maxWidth)); TextBlock block = textBody.addButton(new TextBlock(left, top, maxWidth));
block.getStyle().setText(Text.translatable(key)); block.getStyle().setText(Text.translatable(key));
top += block.getBounds().height; top += block.getBounds().height;
} }
} }
addDrawable(new Label(left - 3, top += 5)).getStyle().setText(Text.translatable("gui.unicopia.tribe_selection.confirm.bads", race).formatted(Formatting.YELLOW)); textBody.addButton(new Label(left - 3, top += 5)).getStyle().setText(Text.translatable("gui.unicopia.tribe_selection.confirm.bads", race).formatted(Formatting.YELLOW));
top += 15; top += 15;
for (int i = 0; i < 5; i++) { for (int i = 0; i < 10; i++) {
String key = String.format("gui.unicopia.tribe_selection.confirm.bads.%d.%s.%s", i, id.getNamespace(), id.getPath()); String key = String.format("gui.unicopia.tribe_selection.confirm.bads.%d.%s.%s", i, id.getNamespace(), id.getPath());
if (Language.getInstance().hasTranslation(key)) { if (Language.getInstance().hasTranslation(key)) {
TextBlock block = addDrawable(new TextBlock(left, top, maxWidth)); TextBlock block = textBody.addButton(new TextBlock(left, top, maxWidth));
block.getStyle().setText(Text.translatable(key)); block.getStyle().setText(Text.translatable(key));
top += block.getBounds().height; top += block.getBounds().height;
} }
@ -81,9 +100,7 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
} }
@Override @Override
public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.renderBackground(context, mouseX, mouseY, delta);
final int columnHeight = 180; final int columnHeight = 180;
final int columnWidth = 310; final int columnWidth = 310;
final int segmentWidth = 123; final int segmentWidth = 123;
@ -91,7 +108,7 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
int top = (height - columnHeight) / 2; int top = (height - columnHeight) / 2;
int left = (width - columnWidth) / 2; int left = (width - columnWidth) / 2;
top += 25; top += 40;
final int zOffset = 0; final int zOffset = 0;
@ -106,10 +123,16 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
left = width / 2; left = width / 2;
context.drawTexture(TribeSelectionScreen.TEXTURE, left - 55, top, 140, 70, 21, 50); context.drawTexture(TribeSelectionScreen.TEXTURE, left - 55, top, 140, 70, 21, 50);
context.drawTexture(TribeSelectionScreen.TEXTURE, left - 35, top, 10, 70, 69, 50);
context.drawTexture(TribeSelectionScreen.TEXTURE, left + 35, top, 148, 70, 21, 50); context.drawTexture(TribeSelectionScreen.TEXTURE, left + 35, top, 148, 70, 21, 50);
textBody.render(context, mouseX, mouseY, delta);
context.getMatrices().push();
context.getMatrices().translate(0, 0, 2);
context.drawTexture(TribeSelectionScreen.TEXTURE, left - 35, top - 5, 10, 70, 69, 50);
context.drawTexture(TribeSelectionScreen.TEXTURE, left - 35, top - 15, 10, 70, 69, 50);
super.render(context, mouseX, mouseY, delta);
context.getMatrices().pop();
} }
@Override @Override
@ -120,7 +143,11 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
@Override @Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) { public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE) { if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
callback.accept(false);
return true;
}
if (keyCode == GLFW.GLFW_KEY_ENTER) {
callback.accept(true);
return true; return true;
} }
return super.keyPressed(keyCode, scanCode, modifiers); return super.keyPressed(keyCode, scanCode, modifiers);

View file

@ -1,8 +1,11 @@
package com.minelittlepony.unicopia.client.gui; package com.minelittlepony.unicopia.client.gui;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.lwjgl.glfw.GLFW;
import com.minelittlepony.common.client.gui.GameGui; import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.element.Label; import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
@ -10,9 +13,12 @@ import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgRequestSpeciesChange; import com.minelittlepony.unicopia.network.MsgRequestSpeciesChange;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
public class TribeSelectionScreen extends GameGui implements HidesHud { public class TribeSelectionScreen extends GameGui implements HidesHud {
static final Identifier TEXTURE = Unicopia.id("textures/gui/tribe_selection.png"); static final Identifier TEXTURE = Unicopia.id("textures/gui/tribe_selection.png");
@ -24,6 +30,13 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
private boolean finished; private boolean finished;
private final List<TribeButton> options = new ArrayList<>();
private static int SELECTION = -1;
private int prevScrollPosition;
private int scrollPosition;
private int targetScroll;
public TribeSelectionScreen(Set<Race> allowedRaces, String baseString) { public TribeSelectionScreen(Set<Race> allowedRaces, String baseString) {
super(Text.translatable(baseString)); super(Text.translatable(baseString));
this.allowedRaces = allowedRaces; this.allowedRaces = allowedRaces;
@ -51,33 +64,26 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
top += block.getBounds().height; top += block.getBounds().height;
top += 30; top += 30;
final int itemWidth = 70 + 10; List<Race> options = Race.REGISTRY.stream().filter(race -> race.availability().isSelectable()).toList();
this.options.clear();
List<Race> options = Race.REGISTRY.stream().filter(race -> !race.isHuman() && !race.isOp()).toList();
int columns = Math.min(width / itemWidth, options.size());
int x = (width - (columns * itemWidth)) / 2;
int y = top;
int column = 0;
int row = 0;
for (Race race : options) { for (Race race : options) {
addOption(race, x + (column * itemWidth), y + (row * itemWidth)); addOption(race, top);
column++;
if (column >= columns) {
column = 0;
row++;
}
} }
top = height - 20; if (SELECTION == -1) {
SELECTION = options.size() / 2;
}
scroll(SELECTION, false);
} }
private void addOption(Race race, int x, int y) { private void addOption(Race race, int y) {
addDrawableChild(new TribeButton(x, y, width, race)).onClick(b -> { var option = new TribeButton(0, y, width, race);
int index = options.size();
options.add(addDrawableChild(option));
option.onClick(b -> {
finished = true; finished = true;
scroll(index, false);
client.setScreen(new TribeConfirmationScreen(result -> { client.setScreen(new TribeConfirmationScreen(result -> {
finished = false; finished = false;
@ -91,12 +97,88 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
}).setEnabled(allowedRaces.contains(race)); }).setEnabled(allowedRaces.contains(race));
} }
@Override
public void tick() {
prevScrollPosition = scrollPosition;
if (scrollPosition < targetScroll) {
scrollPosition++;
}
if (scrollPosition > targetScroll) {
scrollPosition--;
}
}
private void updateScolling() {
final int itemWidth = 70 + 10;
int x = (width - itemWidth) / 2;
float diff = MathHelper.lerp(client.getTickDelta(), prevScrollPosition, scrollPosition) / 4F;
for (int i = 0; i < options.size(); i++) {
var option = options.get(i);
option.setX((int)(x + (i - diff) * itemWidth));
option.setFocused(i == SELECTION);
}
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
updateScolling();
super.render(context, mouseX, mouseY, delta);
if (!options.isEmpty()) {
var element = options.get(0);
float diff = (targetScroll - MathHelper.lerp(client.getTickDelta(), prevScrollPosition, scrollPosition)) * 7;
context.drawTexture(TEXTURE, (width / 2) + 40 + (scrollPosition < targetScroll ? (int)diff : 0), element.getY() - 20, 10, 165, 153, 30, 85, 312, 312);
context.drawTexture(TEXTURE, (width / 2) - 80 + (scrollPosition > targetScroll ? (int)diff : 0), element.getY() - 20, 10, 195, 153, 30, 85, 312, 312);
if (element.getBounds().left < 0) {
context.drawTexture(TEXTURE, 20, element.getY() - 10, 10, 188, 235, 24, 60, 312, 312);
}
element = options.get(options.size() - 1);
if (element.getBounds().right() > width) {
context.drawTexture(TEXTURE, width - 50, element.getY() - 10, 10, 164, 235, 24, 60, 312, 312);
}
}
}
@Override @Override
public void finish() { public void finish() {
finished = true; finished = true;
close(); close();
} }
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_LEFT) {
scroll(Math.max(SELECTION - 1, 0), true);
return true;
}
if (keyCode == GLFW.GLFW_KEY_RIGHT) {
scroll(Math.min(SELECTION + 1, options.size() - 1), true);
return true;
}
if (keyCode == GLFW.GLFW_KEY_ENTER) {
options.get(SELECTION).onPress();
}
return super.keyPressed(keyCode, scanCode, modifiers);
}
private void scroll(int target, boolean animate) {
if (target == SELECTION) {
return;
}
SELECTION = target;
targetScroll = SELECTION * 4;
if (!animate) {
scrollPosition = targetScroll;
prevScrollPosition = scrollPosition;
} else {
playSound(SoundEvents.UI_BUTTON_CLICK);
}
}
@Override @Override
public boolean shouldCloseOnEsc() { public boolean shouldCloseOnEsc() {
return false; return false;

View file

@ -15,6 +15,7 @@ import com.minelittlepony.client.model.entity.race.UnicornModel;
import com.minelittlepony.client.model.part.UnicornHorn; import com.minelittlepony.client.model.part.UnicornHorn;
import com.minelittlepony.mson.api.MsonModel; import com.minelittlepony.mson.api.MsonModel;
import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.AmuletSelectors; import com.minelittlepony.unicopia.entity.AmuletSelectors;
@ -43,7 +44,7 @@ class BodyPartGear<M extends ClientPonyModel<LivingEntity>> implements Gear {
public static final Predicate<LivingEntity> UNICORN_HORN_PREDICATE = MINE_LP_HAS_NO_HORN.and(AmuletSelectors.ALICORN_AMULET.or(EquinePredicates.raceMatches(Race::canCast))); public static final Predicate<LivingEntity> UNICORN_HORN_PREDICATE = MINE_LP_HAS_NO_HORN.and(AmuletSelectors.ALICORN_AMULET.or(EquinePredicates.raceMatches(Race::canCast)));
public static final Identifier UNICORN_HORN = Unicopia.id("textures/models/horn/unicorn.png"); public static final Identifier UNICORN_HORN = Unicopia.id("textures/models/horn/unicorn.png");
public static final Predicate<LivingEntity> PEGA_WINGS_PREDICATE = MINE_LP_HAS_NO_WINGS.and(AmuletSelectors.PEGASUS_AMULET.or(EquinePredicates.raceMatches(Race::canInteractWithClouds))); public static final Predicate<LivingEntity> PEGA_WINGS_PREDICATE = MINE_LP_HAS_NO_WINGS.and(AmuletSelectors.PEGASUS_AMULET.or(EquinePredicates.raceMatches(race -> race.flightType() == FlightType.AVIAN)));
public static final Identifier PEGASUS_WINGS = Unicopia.id("textures/models/wings/pegasus_pony.png"); public static final Identifier PEGASUS_WINGS = Unicopia.id("textures/models/wings/pegasus_pony.png");
public static BodyPartGear<WingsGearModel> pegasusWings() { public static BodyPartGear<WingsGearModel> pegasusWings() {

View file

@ -54,9 +54,9 @@ public class Main extends MineLPDelegate implements ClientModInitializer {
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.CHANGEDLING, Race.CHANGELING); registerRaceMapping(com.minelittlepony.api.pony.meta.Race.CHANGEDLING, Race.CHANGELING);
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.ZEBRA, Race.EARTH); registerRaceMapping(com.minelittlepony.api.pony.meta.Race.ZEBRA, Race.EARTH);
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.GRYPHON, Race.PEGASUS); registerRaceMapping(com.minelittlepony.api.pony.meta.Race.GRYPHON, Race.PEGASUS);
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.HIPPOGRIFF, Race.PEGASUS); registerRaceMapping(com.minelittlepony.api.pony.meta.Race.HIPPOGRIFF, Race.HIPPOGRIFF);
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.BATPONY, Race.BAT); registerRaceMapping(com.minelittlepony.api.pony.meta.Race.BATPONY, Race.BAT);
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.SEAPONY, Race.UNICORN); registerRaceMapping(com.minelittlepony.api.pony.meta.Race.SEAPONY, Race.SEAPONY);
} }
private void onPonyModelPrepared(Entity entity, PonyModel<?> model, ModelAttributes.Mode mode) { private void onPonyModelPrepared(Entity entity, PonyModel<?> model, ModelAttributes.Mode mode) {

View file

@ -24,8 +24,10 @@ public class AccessoryFeatureRenderer<
private static final List<FeatureFactory<?>> REGISTRY = new ArrayList<>(); private static final List<FeatureFactory<?>> REGISTRY = new ArrayList<>();
public static <T extends LivingEntity> void register(FeatureFactory<T> factory) { public static void register(FeatureFactory<?>...factories) {
REGISTRY.add(factory); for (var factory : factories) {
REGISTRY.add(factory);
}
} }
private final Iterable<Feature<T>> features; private final Iterable<Feature<T>> features;

View file

@ -32,7 +32,6 @@ public class ModelPartHooks {
final var bestCandidate = new EnqueudHeadRender(); final var bestCandidate = new EnqueudHeadRender();
matrices.push();
part.forEachCuboid(matrices, (entry, name, index, cube) -> { part.forEachCuboid(matrices, (entry, name, index, cube) -> {
float x = cube.maxX - cube.minX; float x = cube.maxX - cube.minX;
float y = cube.maxY - cube.minY; float y = cube.maxY - cube.minY;
@ -47,7 +46,6 @@ public class ModelPartHooks {
bestCandidate.maxSideLength = Math.max(Math.max(x, z), y); bestCandidate.maxSideLength = Math.max(Math.max(x, z), y);
} }
}); });
matrices.pop();
if (bestCandidate.transformation != null) { if (bestCandidate.transformation != null) {
head.add(bestCandidate); head.add(bestCandidate);
@ -75,8 +73,6 @@ public class ModelPartHooks {
matrices.translate(x * PIXEL_SCALE, y * PIXEL_SCALE, z * PIXEL_SCALE); matrices.translate(x * PIXEL_SCALE, y * PIXEL_SCALE, z * PIXEL_SCALE);
matrices.scale(scale, scale, scale); matrices.scale(scale, scale, scale);
//matrices.peek().getPositionMatrix().scaleAround(scale, x * PIXEL_SCALE, y * PIXEL_SCALE, z * PIXEL_SCALE);
//matrices.translate(cube.minX * PIXEL_SCALE, cube.minY * PIXEL_SCALE, cube.minZ * PIXEL_SCALE);
} }
} }
} }

View file

@ -4,33 +4,40 @@ import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry; import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry.DynamicItemRenderer; import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry.DynamicItemRenderer;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.item.ClampedModelPredicateProvider;
import net.minecraft.client.item.ModelPredicateProviderRegistry; import net.minecraft.client.item.ModelPredicateProviderRegistry;
import net.minecraft.client.model.*; import net.minecraft.client.model.*;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.model.TridentEntityModel;
import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.ModelIdentifier; import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.*; import net.minecraft.item.*;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.RotationAxis;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
public class PolearmRenderer implements DynamicItemRenderer { public class PolearmRenderer implements DynamicItemRenderer, ClampedModelPredicateProvider {
private static final PolearmRenderer INSTANCE = new PolearmRenderer(); private static final PolearmRenderer INSTANCE = new PolearmRenderer();
private static final Identifier THROWING = new Identifier("throwing"); private static final Identifier THROWING = new Identifier("throwing");
private final TridentEntityModel model = new TridentEntityModel(getTexturedModelData().createModel()); private final ModelPart model = getTexturedModelData().createModel();
public static void register(Item item) { public static void register(Item...items) {
BuiltinItemRendererRegistry.INSTANCE.register(item, INSTANCE); for (Item item : items) {
ModelPredicateProviderRegistry.register(item, THROWING, (stack, world, entity, seed) -> { BuiltinItemRendererRegistry.INSTANCE.register(item, INSTANCE);
return entity != null && entity.isUsingItem() && entity.getActiveItem() == stack ? 1 : 0; ModelPredicateProviderRegistry.register(item, THROWING, INSTANCE);
}
ModelLoadingPlugin.register(context -> {
for (Item item : items) {
context.addModels(getModelId(item));
}
}); });
ModelLoadingPlugin.register(context -> context.addModels(getModelId(item)));
} }
static ModelIdentifier getModelId(ItemConvertible item) { static ModelIdentifier getModelId(ItemConvertible item) {
@ -50,6 +57,11 @@ public class PolearmRenderer implements DynamicItemRenderer {
return TexturedModelData.of(data, 32, 32); return TexturedModelData.of(data, 32, 32);
} }
@Override
public float unclampedCall(ItemStack stack, ClientWorld world, LivingEntity entity, int seed) {
return entity != null && entity.isUsingItem() && entity.getActiveItem() == stack ? 1 : 0;
}
@Override @Override
public void render(ItemStack stack, ModelTransformationMode mode, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { public void render(ItemStack stack, ModelTransformationMode mode, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
@ -77,7 +89,7 @@ public class PolearmRenderer implements DynamicItemRenderer {
} }
Identifier id = Registries.ITEM.getId(stack.getItem()); Identifier id = Registries.ITEM.getId(stack.getItem());
Identifier texture = new Identifier(id.getNamespace(), "textures/entity/polearm/" + id.getPath() + ".png"); Identifier texture = new Identifier(id.getNamespace(), "textures/entity/polearm/" + id.getPath() + ".png");
model.render(matrices, ItemRenderer.getDirectItemGlintConsumer(vertexConsumers, model.getLayer(texture), false, stack.hasGlint()), light, overlay, 1, 1, 1, 1); model.render(matrices, ItemRenderer.getDirectItemGlintConsumer(vertexConsumers, RenderLayer.getEntitySolid(texture), false, stack.hasGlint()), light, overlay, 1, 1, 1, 1);
matrices.pop(); matrices.pop();
} }
} }

View file

@ -1,7 +1,6 @@
package com.minelittlepony.unicopia.client.render; package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.Creature;
import com.minelittlepony.unicopia.item.enchantment.WantItNeedItEnchantment; import com.minelittlepony.unicopia.item.enchantment.WantItNeedItEnchantment;
@ -20,7 +19,6 @@ import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
public class SmittenEyesRenderer { public class SmittenEyesRenderer {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/smitten_eyes.png"); private static final Identifier TEXTURE = Unicopia.id("textures/entity/smitten_eyes.png");
@ -42,9 +40,6 @@ public class SmittenEyesRenderer {
ModelPartHooks.stopCollecting().forEach(head -> { ModelPartHooks.stopCollecting().forEach(head -> {
matrices.push(); matrices.push();
head.transform(matrices, 0.95F); head.transform(matrices, 0.95F);
if (MineLPDelegate.getInstance().getRace(pony.asEntity()).isEquine()) {
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-90), 0, 1.2F, 0);
}
float scale = 1F + (1.3F + MathHelper.sin(pony.asEntity().age / 3F) * 0.06F); float scale = 1F + (1.3F + MathHelper.sin(pony.asEntity().age / 3F) * 0.06F);
matrices.scale(scale, scale, scale); matrices.scale(scale, scale, scale);
matrices.translate(0, 0.05F, 0); matrices.translate(0, 0.05F, 0);

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.client.render; package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -50,7 +51,7 @@ public class WingsFeatureRenderer<E extends LivingEntity> implements AccessoryFe
} }
protected boolean canRender(E entity) { protected boolean canRender(E entity) {
return entity instanceof PlayerEntity && Pony.of((PlayerEntity)entity).getObservedSpecies().canInteractWithClouds(); return entity instanceof PlayerEntity && Pony.of((PlayerEntity)entity).getObservedSpecies().flightType() == FlightType.AVIAN;
} }
protected Identifier getTexture(E entity) { protected Identifier getTexture(E entity) {

View file

@ -4,16 +4,21 @@ import java.util.Optional;
import com.minelittlepony.client.util.render.RenderLayerUtil; import com.minelittlepony.client.util.render.RenderLayerUtil;
import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
import com.minelittlepony.unicopia.client.render.model.SphereModel;
import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.Creature;
import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.Equine;
import com.minelittlepony.unicopia.entity.ItemImpl; import com.minelittlepony.unicopia.entity.ItemImpl;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.duck.LavaAffine; import com.minelittlepony.unicopia.entity.duck.LavaAffine;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*; import net.minecraft.client.render.*;
import net.minecraft.client.render.BackgroundRenderer.FogType;
import net.minecraft.client.render.VertexConsumerProvider.Immediate; import net.minecraft.client.render.VertexConsumerProvider.Immediate;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.*; import net.minecraft.entity.*;
@ -43,6 +48,20 @@ public class WorldRenderDelegate {
return Optional.empty(); return Optional.empty();
} }
public void applyFog(Camera camera, FogType fogType, float viewDistance, boolean thickFog, float tickDelta) {
if (camera.getSubmersionType() == CameraSubmersionType.WATER) {
if (EquinePredicates.PLAYER_SEAPONY.test(MinecraftClient.getInstance().player)) {
RenderSystem.setShaderFogStart(RenderSystem.getShaderFogStart() - 30);
RenderSystem.setShaderFogEnd(RenderSystem.getShaderFogEnd() + 190);
}
}
if (camera.getSubmersionType() == CameraSubmersionType.NONE) {
if (EquinePredicates.PLAYER_SEAPONY.test(MinecraftClient.getInstance().player)) {
RenderSystem.setShaderFogStart(-130);
}
}
}
public boolean beforeEntityRender(Entity entity, public boolean beforeEntityRender(Entity entity,
double x, double y, double z, float yaw, double x, double y, double z, float yaw,
float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) { float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
@ -80,6 +99,27 @@ public class WorldRenderDelegate {
smittenEyesRenderer.render(creature, matrices, immediate, light, 0); smittenEyesRenderer.render(creature, matrices, immediate, light, 0);
} }
if (pony instanceof Pony p) {
if (p.getCompositeRace().includes(Race.SEAPONY)
&& pony.asEntity().isSubmergedInWater()
&& MineLPDelegate.getInstance().getPlayerPonyRace(p.asEntity()) != Race.SEAPONY) {
for (var head : ModelPartHooks.stopCollecting()) {
matrices.push();
head.transform(matrices, 1F);
Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers();
RenderLayer layer = RenderLayers.getMagicColored();
float scale = 0.9F;
SphereModel.SPHERE.render(matrices, immediate.getBuffer(layer), light, 0, scale, 0.5F, 0.5F, 0.5F, 0.1F);
SphereModel.SPHERE.render(matrices, immediate.getBuffer(layer), light, 0, scale + 0.2F, 0.5F, 0.5F, 0.5F, 0.1F);
matrices.pop();
}
}
}
if (pony instanceof ItemImpl || pony instanceof Living) { if (pony instanceof ItemImpl || pony instanceof Living) {
matrices.pop(); matrices.pop();
@ -162,6 +202,13 @@ public class WorldRenderDelegate {
roll -= 180; roll -= 180;
} }
if (p.getAcrobatics().isFloppy()) {
matrices.translate(0, -0.5, 0);
p.asEntity().setBodyYaw(0);
p.asEntity().setYaw(0);
matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(90));
}
matrices.multiply(RotationAxis.NEGATIVE_Y.rotationDegrees(yaw)); matrices.multiply(RotationAxis.NEGATIVE_Y.rotationDegrees(yaw));
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(roll)); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(roll));
@ -169,6 +216,12 @@ public class WorldRenderDelegate {
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(diveAngle)); matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(diveAngle));
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw)); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
if (p.getCompositeRace().includes(Race.SEAPONY)
&& pony.asEntity().isSubmergedInWater()
&& MineLPDelegate.getInstance().getPlayerPonyRace(p.asEntity()) != Race.SEAPONY) {
ModelPartHooks.startCollecting();
}
} else if (pony instanceof Creature creature && smittenEyesRenderer.isSmitten(creature)) { } else if (pony instanceof Creature creature && smittenEyesRenderer.isSmitten(creature)) {
ModelPartHooks.startCollecting(); ModelPartHooks.startCollecting();
} }

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.client.render.entity;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.block.FancyBedBlock.SheetPattern;
import com.minelittlepony.unicopia.block.cloud.CloudBedBlock; import com.minelittlepony.unicopia.block.cloud.CloudBedBlock;
import com.minelittlepony.unicopia.client.render.RenderLayers; import com.minelittlepony.unicopia.client.render.RenderLayers;
@ -18,6 +19,7 @@ import net.minecraft.client.model.ModelPartBuilder;
import net.minecraft.client.model.ModelPartData; import net.minecraft.client.model.ModelPartData;
import net.minecraft.client.model.ModelTransform; import net.minecraft.client.model.ModelTransform;
import net.minecraft.client.model.TexturedModelData; import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.BlockEntityRenderer; import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory; import net.minecraft.client.render.block.entity.BlockEntityRendererFactory;
@ -30,14 +32,19 @@ import net.minecraft.util.math.RotationAxis;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBedBlock.Tile> { public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBedBlock.Tile> {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/cloud_bed/white.png"); private final ModelPart bedHead = getHeadTexturedModelData().createModel();
private final ModelPart bedFoot = getFootTexturedModelData().createModel();
private final ModelPart bedHead; private final ModelPart bedSheetsHead = getSheetsTexturedModelData(0, 3, 13).createModel();
private final ModelPart bedFoot; private final ModelPart bedSheetsFoot = getSheetsTexturedModelData(22, 0, 15).createModel();
public CloudBedBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) { public CloudBedBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) {
this.bedHead = getHeadTexturedModelData().createModel(); }
this.bedFoot = getFootTexturedModelData().createModel();
public static TexturedModelData getSheetsTexturedModelData(int v, int y, int height) {
ModelData data = new ModelData();
ModelPartData root = data.getRoot();
root.addChild("main", ModelPartBuilder.create().uv(0, v).cuboid(0, y, 0, 16, height, 6), ModelTransform.NONE);
return TexturedModelData.of(data, 64, 64);
} }
public static TexturedModelData getHeadTexturedModelData() { public static TexturedModelData getHeadTexturedModelData() {
@ -66,9 +73,19 @@ public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBed
public void render(CloudBedBlock.Tile entity, float f, MatrixStack matrices, VertexConsumerProvider vertices, int light, int overlay) { public void render(CloudBedBlock.Tile entity, float f, MatrixStack matrices, VertexConsumerProvider vertices, int light, int overlay) {
@Nullable @Nullable
World world = entity.getWorld(); World world = entity.getWorld();
CloudBedBlock.SheetPattern pattern = entity.getPattern();
VertexConsumer buffer = getBuffer(vertices, entity.getBase());
if (world == null) { if (world == null) {
renderModel(matrices, vertices, bedHead, Direction.SOUTH, TEXTURE, light, overlay, false); renderModel(matrices, vertices, bedHead, Direction.SOUTH, buffer, light, overlay, false, false);
renderModel(matrices, vertices, bedFoot, Direction.SOUTH, TEXTURE, light, overlay, true); renderModel(matrices, vertices, bedFoot, Direction.SOUTH, buffer, light, overlay, true, false);
if (pattern != CloudBedBlock.SheetPattern.NONE) {
buffer = getSheetsBuffer(vertices, pattern);
renderModel(matrices, vertices, bedSheetsHead, Direction.SOUTH, buffer, light, overlay, false, true);
renderModel(matrices, vertices, bedSheetsFoot, Direction.SOUTH, buffer, light, overlay, true, true);
}
return; return;
} }
@ -77,11 +94,36 @@ public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBed
renderModel(matrices, vertices, renderModel(matrices, vertices,
state.get(BedBlock.PART) == BedPart.HEAD ? bedHead : bedFoot, state.get(BedBlock.PART) == BedPart.HEAD ? bedHead : bedFoot,
state.get(BedBlock.FACING), state.get(BedBlock.FACING),
TEXTURE, buffer,
getModelLight(entity, light), getModelLight(entity, light),
overlay, overlay,
false,
false false
); );
if (pattern != CloudBedBlock.SheetPattern.NONE) {
renderModel(matrices, vertices,
state.get(BedBlock.PART) == BedPart.HEAD ? bedSheetsHead : bedSheetsFoot,
state.get(BedBlock.FACING),
getSheetsBuffer(vertices, pattern),
getModelLight(entity, light),
overlay,
false,
true
);
}
}
private VertexConsumer getBuffer(VertexConsumerProvider vertices, String base) {
Identifier texture = Unicopia.id("textures/entity/bed/" + base + ".png");
return vertices.getBuffer(base.equalsIgnoreCase("cloud")
? RenderLayers.getEntityTranslucent(texture)
: RenderLayers.getEntityCutout(texture)
);
}
private VertexConsumer getSheetsBuffer(VertexConsumerProvider vertices, SheetPattern pattern) {
Identifier sheetsTexture = Unicopia.id("textures/entity/bed/sheets/" + pattern.asString() + ".png");
return vertices.getBuffer(RenderLayers.getEntityCutout(sheetsTexture));
} }
private int getModelLight(CloudBedBlock.Tile entity, int worldLight) { private int getModelLight(CloudBedBlock.Tile entity, int worldLight) {
@ -95,14 +137,18 @@ public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBed
).apply(new LightmapCoordinatesRetriever<>()).get(worldLight); ).apply(new LightmapCoordinatesRetriever<>()).get(worldLight);
} }
private void renderModel(MatrixStack matrices, VertexConsumerProvider vertices, ModelPart part, Direction direction, Identifier texture, int light, int overlay, boolean translate) { private void renderModel(MatrixStack matrices, VertexConsumerProvider vertices, ModelPart part, Direction direction, VertexConsumer buffer, int light, int overlay, boolean translate, boolean sheets) {
matrices.push(); matrices.push();
matrices.translate(0, 0.5625f, translate ? -1 : 0); matrices.translate(0, 0.5625f, translate ? -1 : 0);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90)); matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90));
matrices.translate(0.5f, 0.5f, 0.5f); matrices.translate(0.5f, 0.5f, 0.5f);
if (sheets) {
float beddingScale = 1.002F;
matrices.scale(beddingScale, beddingScale, beddingScale);
}
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180 + direction.asRotation())); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180 + direction.asRotation()));
matrices.translate(-0.5f, -0.5f, -0.5f); matrices.translate(-0.5f, -0.5f, -0.5f);
part.render(matrices, vertices.getBuffer(RenderLayers.getEntityTranslucent(texture)), light, overlay); part.render(matrices, buffer, light, overlay);
matrices.pop(); matrices.pop();
} }
} }

View file

@ -0,0 +1,125 @@
package com.minelittlepony.unicopia.client.render.entity;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.block.AbstractChestBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.DoubleBlockProperties;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.block.enums.ChestType;
import net.minecraft.client.model.Dilation;
import net.minecraft.client.model.ModelData;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.model.ModelPartBuilder;
import net.minecraft.client.model.ModelPartData;
import net.minecraft.client.model.ModelTransform;
import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory.Context;
import net.minecraft.client.render.block.entity.ChestBlockEntityRenderer;
import net.minecraft.client.render.block.entity.LightmapCoordinatesRetriever;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.RotationAxis;
public class CloudChestBlockEntityRenderer extends ChestBlockEntityRenderer<ChestBlockEntity> {
private static final LightmapCoordinatesRetriever<ChestBlockEntity> LIGHTING = new LightmapCoordinatesRetriever<>();
private final Model[] models;
public CloudChestBlockEntityRenderer(Context ctx) {
super(ctx);
models = new Model[] {
new Model(Model.getSingleChestModelData().createModel(), Unicopia.id("textures/entity/chest/cloud.png")),
new Model(Model.getLeftChestModelData().createModel(), Unicopia.id("textures/entity/chest/cloud_left.png")),
new Model(Model.getRightChestModelData().createModel(), Unicopia.id("textures/entity/chest/cloud_right.png"))
};
}
@Override
public void render(ChestBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
BlockState state = entity.getWorld() != null ? entity.getCachedState() : Blocks.CHEST.getDefaultState().with(ChestBlock.FACING, Direction.SOUTH);
if (!(state.getBlock() instanceof AbstractChestBlock)) {
return;
}
Model model = models[state.getOrEmpty(ChestBlock.CHEST_TYPE).orElse(ChestType.SINGLE).ordinal()];
var properties = getProperties(state, entity);
matrices.push();
matrices.translate(0.5f, 0.5f, 0.5f);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-state.get(ChestBlock.FACING).asRotation()));
matrices.translate(-0.5f, -0.5f, -0.5f);
model.setAngles(1 - (float)Math.pow(1 - properties.apply(ChestBlock.getAnimationProgressRetriever(entity)).get(tickDelta), 3));
model.render(matrices, vertexConsumers.getBuffer(RenderLayer.getEntityTranslucent(model.texture)), properties.apply(LIGHTING).applyAsInt(light), overlay);
matrices.pop();
}
private DoubleBlockProperties.PropertySource<? extends ChestBlockEntity> getProperties(BlockState state, ChestBlockEntity entity) {
return entity.getWorld() != null
? ((AbstractChestBlock<?>)state.getBlock()).getBlockEntitySource(state, entity.getWorld(), entity.getPos(), true)
: DoubleBlockProperties.PropertyRetriever::getFallback;
}
static class Model {
private final ModelPart tree;
private final ModelPart lid;
private final Identifier texture;
public Model(ModelPart tree, Identifier texture) {
this.tree = tree;
this.lid = tree.getChild("lid");
this.texture = texture;
}
public static TexturedModelData getSingleChestModelData() {
ModelData data = new ModelData();
ModelPartData root = data.getRoot();
root.addChild("chest", ModelPartBuilder.create().uv(0, 19).cuboid(1, 0, 1, 14, 10, 14, Dilation.NONE), ModelTransform.NONE);
root.addChild("lid", ModelPartBuilder.create()
.uv(0, 0).cuboid(6, -2, 13.8F, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(6, -1, 14, 2, 2, 1, Dilation.NONE)
.uv(0, 0).cuboid(0, 0, 0, 14, 5, 14, new Dilation(0.3F)), ModelTransform.pivot(1, 9, 1))
.addChild("lock_r1", ModelPartBuilder.create()
.uv(0, 0).cuboid(-2, -4, -0.5F, 2, 4, 1, Dilation.NONE), ModelTransform.of(5, 1, 14.3F, 0, 0, 1.5708F));
return TexturedModelData.of(data, 64, 64);
}
public static TexturedModelData getLeftChestModelData() {
ModelData data = new ModelData();
ModelPartData root = data.getRoot();
root.addChild("chest", ModelPartBuilder.create().uv(0, 19).cuboid(0, 0, 1, 15, 10, 14, Dilation.NONE), ModelTransform.NONE);
root.addChild("lid", ModelPartBuilder.create()
.uv(0, 0).cuboid(6, -2, 13.8F, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(6, -1, 14, 2, 2, 1, Dilation.NONE)
.uv(0, 0).cuboid(0, 0, 0, 15, 5, 14, new Dilation(0.3F)), ModelTransform.pivot(0, 9, 1))
.addChild("lock_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-2, -4, -0.5F, 2, 4, 1, Dilation.NONE), ModelTransform.of(5, 1, 14.3F, 0, 0, 1.5708F));
return TexturedModelData.of(data, 64, 64);
}
public static TexturedModelData getRightChestModelData() {
ModelData data = new ModelData();
ModelPartData root = data.getRoot();
root.addChild("chest", ModelPartBuilder.create().uv(0, 19).cuboid(1, 0, 1, 15, 10, 14, Dilation.NONE), ModelTransform.NONE);
root.addChild("lid", ModelPartBuilder.create()
.uv(0, 0).cuboid(7, -2, 13.8F, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(7, -1, 14, 2, 2, 1, Dilation.NONE)
.uv(0, 0).cuboid(0, 0, 0, 15, 5, 14, new Dilation(0.3F)), ModelTransform.pivot(1, 9, 1))
.addChild("lock_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-2, -4, -0.5F, 2, 4, 1, Dilation.NONE), ModelTransform.of(6, 1, 14.3F, 0, 0, 1.5708F));
return TexturedModelData.of(data, 64, 64);
}
public void setAngles(float animationProgress) {
lid.pitch = -(animationProgress * 1.5707964f);
}
public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay) {
tree.render(matrices, vertices, light, overlay);
}
}
}

View file

@ -0,0 +1,28 @@
package com.minelittlepony.unicopia.client.render.entity;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.client.render.entity.EntityRendererFactory.Context;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.mob.SilverfishEntity;
import net.minecraft.util.Identifier;
import net.minecraft.client.render.entity.SilverfishEntityRenderer;
public class LootBugEntityRenderer extends SilverfishEntityRenderer {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/loot_bug.png");
public LootBugEntityRenderer(Context context) {
super(context);
}
@Override
public Identifier getTexture(SilverfishEntity entity) {
return TEXTURE;
}
@Override
protected void scale(SilverfishEntity entity, MatrixStack matrices, float tickDelta) {
float scale = 2;
matrices.scale(scale, scale, scale);
}
}

View file

@ -8,7 +8,6 @@ import com.minelittlepony.unicopia.network.MsgTribeSelect;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.command.argument.RegistryKeyArgumentType;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.CommandManager.RegistrationEnvironment; import net.minecraft.server.command.CommandManager.RegistrationEnvironment;
@ -32,8 +31,6 @@ class SpeciesCommand {
} }
} }
RegistryKeyArgumentType<Race> raceArgument = Race.argument();
return builder return builder
.then(CommandManager.literal("get") .then(CommandManager.literal("get")
.executes(context -> get(context.getSource(), context.getSource().getPlayer(), true)) .executes(context -> get(context.getSource(), context.getSource().getPlayer(), true))
@ -41,13 +38,13 @@ class SpeciesCommand {
.executes(context -> get(context.getSource(), EntityArgumentType.getPlayer(context, "target"), false)) .executes(context -> get(context.getSource(), EntityArgumentType.getPlayer(context, "target"), false))
)) ))
.then(CommandManager.literal("set") .then(CommandManager.literal("set")
.then(CommandManager.argument("race", raceArgument) .then(CommandManager.argument("race", Race.argument())
.executes(context -> set(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), true)) .executes(context -> set(context.getSource(), context.getSource().getPlayer(), Race.fromArgument(context, "race"), true))
.then(CommandManager.argument("target", EntityArgumentType.player()) .then(CommandManager.argument("target", EntityArgumentType.player())
.executes(context -> set(context.getSource(), EntityArgumentType.getPlayer(context, "target"), Race.fromArgument(context, "race"), false))) .executes(context -> set(context.getSource(), EntityArgumentType.getPlayer(context, "target"), Race.fromArgument(context, "race"), false)))
)) ))
.then(CommandManager.literal("describe") .then(CommandManager.literal("describe")
.then(CommandManager.argument("race", raceArgument) .then(CommandManager.argument("race", Race.argument())
.executes(context -> describe(context.getSource().getPlayer(), Race.fromArgument(context, "race"))) .executes(context -> describe(context.getSource().getPlayer(), Race.fromArgument(context, "race")))
)) ))
.then(CommandManager.literal("list") .then(CommandManager.literal("list")
@ -101,7 +98,7 @@ class SpeciesCommand {
boolean first = true; boolean first = true;
for (Race i : Race.REGISTRY) { for (Race i : Race.REGISTRY) {
if (!i.isUnset() && i.isPermitted(player)) { if (i.availability().isGrantable() && !i.isUnset() && i.isPermitted(player)) {
message.append(Text.literal((!first ? "\n" : "") + " - ")); message.append(Text.literal((!first ? "\n" : "") + " - "));
message.append(i.getDisplayName()); message.append(i.getDisplayName());
first = false; first = false;

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellEnhancingRe
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellShapedCraftingRecipe; import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellShapedCraftingRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.item.EnchantableItem; import com.minelittlepony.unicopia.item.EnchantableItem;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.URecipes; import com.minelittlepony.unicopia.item.URecipes;
@ -22,6 +23,7 @@ import dev.emi.emi.api.recipe.EmiRecipeCategory;
import dev.emi.emi.api.render.EmiTexture; import dev.emi.emi.api.render.EmiTexture;
import dev.emi.emi.api.stack.Comparison; import dev.emi.emi.api.stack.Comparison;
import dev.emi.emi.api.stack.EmiStack; import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.recipe.EmiStonecuttingRecipe;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.recipe.RecipeType; import net.minecraft.recipe.RecipeType;
import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.registry.DynamicRegistryManager;
@ -30,7 +32,9 @@ import net.minecraft.util.Identifier;
public class Main implements EmiPlugin { public class Main implements EmiPlugin {
static final EmiStack SPELL_BOOK_STATION = EmiStack.of(UItems.SPELLBOOK); static final EmiStack SPELL_BOOK_STATION = EmiStack.of(UItems.SPELLBOOK);
static final EmiStack CLOUD_SHAPING_STATION = EmiStack.of(UBlocks.SHAPING_BENCH);
static final EmiRecipeCategory SPELL_BOOK_CATEGORY = new EmiRecipeCategory(Unicopia.id("spellbook"), SPELL_BOOK_STATION, SPELL_BOOK_STATION); static final EmiRecipeCategory SPELL_BOOK_CATEGORY = new EmiRecipeCategory(Unicopia.id("spellbook"), SPELL_BOOK_STATION, SPELL_BOOK_STATION);
static final EmiRecipeCategory CLOUD_SHAPING_CATEGORY = new EmiRecipeCategory(Unicopia.id("cloud_shaping"), CLOUD_SHAPING_STATION, CLOUD_SHAPING_STATION);
static final Identifier WIDGETS = Unicopia.id("textures/gui/widgets.png"); static final Identifier WIDGETS = Unicopia.id("textures/gui/widgets.png");
static final EmiTexture EMPTY_ARROW = new EmiTexture(WIDGETS, 44, 0, 24, 17); static final EmiTexture EMPTY_ARROW = new EmiTexture(WIDGETS, 44, 0, 24, 17);
@ -70,6 +74,17 @@ public class Main implements EmiPlugin {
} }
}); });
registry.addCategory(CLOUD_SHAPING_CATEGORY);
registry.addWorkstation(CLOUD_SHAPING_CATEGORY, CLOUD_SHAPING_STATION);
registry.getRecipeManager().listAllOfType(URecipes.CLOUD_SHAPING).forEach(recipe -> {
registry.addRecipe(new EmiStonecuttingRecipe(recipe.value()) {
@Override
public EmiRecipeCategory getCategory() {
return CLOUD_SHAPING_CATEGORY;
}
});
});
Stream.of(UItems.GEMSTONE, UItems.BOTCHED_GEM, UItems.MAGIC_STAFF, UItems.FILLED_JAR).forEach(item -> { Stream.of(UItems.GEMSTONE, UItems.BOTCHED_GEM, UItems.MAGIC_STAFF, UItems.FILLED_JAR).forEach(item -> {
registry.setDefaultComparison(item, comparison -> Comparison.compareNbt()); registry.setDefaultComparison(item, comparison -> Comparison.compareNbt());
}); });

View file

@ -6,6 +6,7 @@ import java.util.stream.Stream;
import com.minelittlepony.unicopia.EntityConvertable; import com.minelittlepony.unicopia.EntityConvertable;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler; import com.minelittlepony.unicopia.container.SpellbookScreenHandler;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
@ -67,7 +68,7 @@ public interface TrinketsDelegate {
} }
default Set<Identifier> getAvailableTrinketSlots(LivingEntity entity, Set<Identifier> probedSlots) { default Set<Identifier> getAvailableTrinketSlots(LivingEntity entity, Set<Identifier> probedSlots) {
return probedSlots.stream().filter(slot -> getEquipped(entity, slot).count() == 0).collect(Collectors.toSet()); return probedSlots.stream().filter(slot -> getEquipped(entity, slot).anyMatch(ItemStack::isEmpty)).collect(Collectors.toSet());
} }
default Stream<ItemStack> getEquipped(LivingEntity entity, Identifier slot) { default Stream<ItemStack> getEquipped(LivingEntity entity, Identifier slot) {

View file

@ -0,0 +1,96 @@
package com.minelittlepony.unicopia.container;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.item.URecipes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.StonecutterScreenHandler;
import net.minecraft.screen.slot.Slot;
import net.minecraft.world.World;
public class ShapingBenchScreenHandler extends StonecutterScreenHandler {
private final ScreenHandlerContext context;
private final World world;
private ItemStack inputStack = ItemStack.EMPTY;
public ShapingBenchScreenHandler(int syncId, PlayerInventory playerInventory, ScreenHandlerContext context) {
super(syncId, playerInventory, context);
this.context = context;
this.world = playerInventory.player.getWorld();
}
public ShapingBenchScreenHandler(int syncId, PlayerInventory playerInventory) {
super(syncId, playerInventory);
this.context = ScreenHandlerContext.EMPTY;
this.world = playerInventory.player.getWorld();
}
@Override
public ScreenHandlerType<?> getType() {
return UScreenHandlers.SHAPING_BENCH;
}
@Override
public boolean canUse(PlayerEntity player) {
return canUse(context, player, UBlocks.SHAPING_BENCH);
}
@Override
public void onContentChanged(Inventory inventory) {
ItemStack stack = slots.get(0).getStack();
if (!stack.isOf(inputStack.getItem())) {
inputStack = stack.copy();
getAvailableRecipes().clear();
setProperty(0, -1);
slots.get(1).setStackNoCallbacks(ItemStack.EMPTY);
if (!stack.isEmpty()) {
getAvailableRecipes().addAll(world.getRecipeManager().getAllMatches(URecipes.CLOUD_SHAPING, input, world));
}
}
}
@Override
public ItemStack quickMove(PlayerEntity player, int slot) {
ItemStack originalStack = ItemStack.EMPTY;
Slot srcSlot = slots.get(slot);
if (srcSlot != null && srcSlot.hasStack()) {
ItemStack movingStack = srcSlot.getStack();
Item item = movingStack.getItem();
originalStack = movingStack.copy();
if (slot == 1) {
item.onCraft(movingStack, player.getWorld(), player);
if (!insertItem(movingStack, 2, 38, true)) {
return ItemStack.EMPTY;
}
srcSlot.onQuickTransfer(movingStack, originalStack);
} else if (slot == 0
? !insertItem(movingStack, 2, 38, false)
: (world.getRecipeManager().getFirstMatch(URecipes.CLOUD_SHAPING, new SimpleInventory(movingStack), world).isPresent()
? !insertItem(movingStack, 0, 1, false)
: (slot >= 2 && slot < 29
? !insertItem(movingStack, 29, 38, false)
: slot >= 29 && slot < 38 && !insertItem(movingStack, 2, 29, false)))) {
return ItemStack.EMPTY;
}
if (movingStack.isEmpty()) {
srcSlot.setStack(ItemStack.EMPTY);
}
srcSlot.markDirty();
if (movingStack.getCount() == originalStack.getCount()) {
return ItemStack.EMPTY;
}
srcSlot.onTakeItem(player, movingStack);
this.sendContentUpdates();
}
return originalStack;
}
}

View file

@ -6,10 +6,12 @@ import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType;
import net.minecraft.screen.ScreenHandler; import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType; import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.registry.Registry; import net.minecraft.registry.Registry;
import net.minecraft.resource.featuretoggle.FeatureFlags;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
public interface UScreenHandlers { public interface UScreenHandlers {
ScreenHandlerType<SpellbookScreenHandler> SPELL_BOOK = register("spell_book", new ExtendedScreenHandlerType<>(SpellbookScreenHandler::new)); ScreenHandlerType<SpellbookScreenHandler> SPELL_BOOK = register("spell_book", new ExtendedScreenHandlerType<>(SpellbookScreenHandler::new));
ScreenHandlerType<ShapingBenchScreenHandler> SHAPING_BENCH = register("shaping_bench", new ScreenHandlerType<>(ShapingBenchScreenHandler::new, FeatureFlags.VANILLA_FEATURES));
static <T extends ScreenHandler> ScreenHandlerType<T> register(String name, ScreenHandlerType<T> type) { static <T extends ScreenHandler> ScreenHandlerType<T> register(String name, ScreenHandlerType<T> type) {
return Registry.register(Registries.SCREEN_HANDLER, Unicopia.id(name), type); return Registry.register(Registries.SCREEN_HANDLER, Unicopia.id(name), type);

View file

@ -0,0 +1,22 @@
package com.minelittlepony.unicopia.diet;
import com.minelittlepony.unicopia.diet.affliction.Affliction;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.network.PacketByteBuf;
public record Ailment(Affliction effects) {
public static final Ailment EMPTY = new Ailment(Affliction.EMPTY);
public static final Codec<Ailment> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Affliction.CODEC.fieldOf("effects").forGetter(Ailment::effects)
).apply(instance, Ailment::new));
public Ailment(PacketByteBuf buffer) {
this(Affliction.read(buffer));
}
public void toBuffer(PacketByteBuf buffer) {
Affliction.write(buffer, effects);
}
}

View file

@ -0,0 +1,157 @@
package com.minelittlepony.unicopia.diet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.item.ItemDuck;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.UseAction;
public record DietProfile(
float defaultMultiplier,
float foragingMultiplier,
List<Multiplier> multipliers,
List<Effect> effects,
Optional<Effect> defaultEffect
) {
public static final DietProfile EMPTY = new DietProfile(1, 1, List.of(), List.of(), Optional.empty());
public static final Codec<DietProfile> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.FLOAT.fieldOf("default_multiplier").forGetter(DietProfile::defaultMultiplier),
Codec.FLOAT.fieldOf("foraging_multiplier").forGetter(DietProfile::foragingMultiplier),
Codec.list(Multiplier.CODEC).fieldOf("multipliers").forGetter(DietProfile::multipliers),
Codec.list(Effect.CODEC).fieldOf("effects").forGetter(DietProfile::effects),
Effect.CODEC.optionalFieldOf("default_effect").forGetter(DietProfile::defaultEffect)
).apply(instance, DietProfile::new));
public DietProfile(PacketByteBuf buffer) {
this(buffer.readFloat(), buffer.readFloat(), buffer.readList(Multiplier::new), buffer.readList(Effect::new), buffer.readOptional(Effect::new));
}
public void toBuffer(PacketByteBuf buffer) {
buffer.writeFloat(defaultMultiplier);
buffer.writeFloat(foragingMultiplier);
buffer.writeCollection(multipliers, (b, t) -> t.toBuffer(b));
buffer.writeCollection(effects, (b, t) -> t.toBuffer(b));
buffer.writeOptional(defaultEffect, (b, t) -> t.toBuffer(b));
}
public Optional<Multiplier> findMultiplier(ItemStack stack) {
return multipliers.stream().filter(m -> m.test(stack)).findFirst();
}
public Optional<Effect> findEffect(ItemStack stack) {
return effects.stream().filter(m -> m.test(stack)).findFirst().or(this::defaultEffect);
}
static boolean isForaged(ItemStack stack) {
return ((ItemDuck)stack.getItem()).getOriginalFoodComponent().isEmpty();
}
@Nullable
public FoodComponent getAdjustedFoodComponent(ItemStack stack) {
var food = stack.getItem().getFoodComponent();
if (this == EMPTY) {
return food;
}
var ratios = getRatios(stack);
if (isInedible(ratios)) {
return null;
}
return FoodAttributes.copy(food)
.hunger(Math.max(1, (int)(food.getHunger() * ratios.getFirst())))
.saturationModifier(food.getSaturationModifier() * ratios.getSecond())
.build();
}
public boolean isInedible(ItemStack stack) {
return isInedible(getRatios(stack));
}
public boolean isInedible(Pair<Float, Float> ratios) {
return ratios.getFirst() <= 0.01F && ratios.getSecond() <= 0.01F;
}
public Pair<Float, Float> getRatios(ItemStack stack) {
Optional<Multiplier> multiplier = findMultiplier(stack);
float baseMultiplier = (isForaged(stack) ? foragingMultiplier() : defaultMultiplier());
float hungerMultiplier = multiplier.map(Multiplier::hunger).orElse(baseMultiplier);
float saturationMultiplier = multiplier.map(Multiplier::saturation).orElse(baseMultiplier);
return Pair.of(hungerMultiplier, saturationMultiplier);
}
public void appendTooltip(ItemStack stack, @Nullable PlayerEntity user, List<Text> tooltip, TooltipContext context) {
var food = stack.getItem().getFoodComponent();
var ratios = getRatios(stack);
if (food == null || isInedible(ratios)) {
if (stack.getUseAction() != UseAction.DRINK) {
tooltip.add(Text.literal(" ").append(Text.translatable("unicopia.diet.not_edible")).formatted(Formatting.DARK_GRAY));
}
return;
}
float baseMultiplier = (isForaged(stack) ? foragingMultiplier() : defaultMultiplier());
if (context.isAdvanced()) {
tooltip.add(Text.literal(" ").append(Text.translatable("unicopia.diet.base_multiplier", baseMultiplier).formatted(Formatting.DARK_GRAY)));
tooltip.add(Text.literal(" ").append(Text.translatable("unicopia.diet.hunger.detailed", Math.max(1, (int)(ratios.getFirst() * food.getHunger())), food.getHunger(), (int)(ratios.getFirst() * 100))).formatted(Formatting.DARK_GRAY));
tooltip.add(Text.literal(" ").append(Text.translatable("unicopia.diet.saturation.detailed", String.format("%.2f", ratios.getSecond() * food.getSaturationModifier()), (int)(ratios.getSecond() * 100))).formatted(Formatting.DARK_GRAY));
} else {
tooltip.add(Text.literal(" ").append(Text.translatable("unicopia.diet.hunger", (int)(ratios.getFirst() * 100))).formatted(Formatting.DARK_GRAY));
tooltip.add(Text.literal(" ").append(Text.translatable("unicopia.diet.saturation", (int)(ratios.getSecond() * 100))).formatted(Formatting.DARK_GRAY));
}
}
public record Multiplier(
Set<TagKey<Item>> tags,
float hunger,
float saturation
) implements Predicate<ItemStack> {
public static final Codec<Set<TagKey<Item>>> TAGS_CODEC = Codec.list(TagKey.unprefixedCodec(RegistryKeys.ITEM)).xmap(
l -> l.stream().distinct().collect(Collectors.toSet()),
set -> new ArrayList<>(set)
);
public static final Codec<Multiplier> CODEC = RecordCodecBuilder.create(instance -> instance.group(
TAGS_CODEC.fieldOf("tags").forGetter(Multiplier::tags),
Codec.FLOAT.fieldOf("hunger").forGetter(Multiplier::hunger),
Codec.FLOAT.fieldOf("saturation").forGetter(Multiplier::saturation)
).apply(instance, Multiplier::new));
public Multiplier(PacketByteBuf buffer) {
this(buffer.readCollection(HashSet::new, p -> TagKey.of(RegistryKeys.ITEM, p.readIdentifier())), buffer.readFloat(), buffer.readFloat());
}
@Override
public boolean test(ItemStack stack) {
return tags.stream().anyMatch(tag -> stack.isIn(tag));
}
public void toBuffer(PacketByteBuf buffer) {
buffer.writeCollection(tags, (p, t) -> p.writeIdentifier(t.id()));
buffer.writeFloat(hunger);
buffer.writeFloat(saturation);
}
}
}

View file

@ -0,0 +1,27 @@
package com.minelittlepony.unicopia.diet;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;
public interface DietView {
TypedActionResult<ItemStack> startUsing(ItemStack stack, World world, PlayerEntity user, Hand hand);
void finishUsing(ItemStack stack, World world, LivingEntity entity);
void appendTooltip(ItemStack stack, @Nullable PlayerEntity user, List<Text> tooltip, TooltipContext context);
interface Holder {
default DietView getDiets(ItemStack stack) {
return PonyDiets.getInstance();
}
}
}

View file

@ -0,0 +1,85 @@
package com.minelittlepony.unicopia.diet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.slf4j.Logger;
import com.google.gson.JsonElement;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.Resources;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.JsonOps;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.minecraft.resource.JsonDataLoader;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.profiler.Profiler;
public class DietsLoader implements IdentifiableResourceReloadListener {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Identifier ID = Unicopia.id("diets");
@Override
public Identifier getFabricId() {
return ID;
}
@Override
public CompletableFuture<Void> reload(Synchronizer sync, ResourceManager manager,
Profiler prepareProfiler, Profiler applyProfiler,
Executor prepareExecutor, Executor applyExecutor) {
var dietsLoadTask = loadData(manager, prepareExecutor, "diets/races").thenApplyAsync(data -> {
Map<Race, DietProfile> profiles = new HashMap<>();
for (var entry : data.entrySet()) {
Identifier id = entry.getKey();
try {
Race.REGISTRY.getOrEmpty(id).ifPresentOrElse(race -> {
DietProfile.CODEC.parse(JsonOps.INSTANCE, entry.getValue())
.resultOrPartial(error -> LOGGER.error("Could not load diet profile {}: {}", id, error))
.ifPresent(profile -> profiles.put(race, profile));
}, () -> LOGGER.warn("Skipped diet for unknown race: " + id));
} catch (Throwable t) {
LOGGER.error("Could not load diet profile {}", id, t);
}
}
return profiles;
}, prepareExecutor);
var effectsLoadTask = loadData(manager, prepareExecutor, "diets/food_effects").thenApplyAsync(data -> data.entrySet().stream()
.map(entry -> {
try {
return Effect.CODEC.parse(JsonOps.INSTANCE, entry.getValue())
.resultOrPartial(error -> LOGGER.error("Could not load food effect {}: {}", entry.getKey(), error));
} catch (Throwable t) {
LOGGER.error("Could not load food effects {}", entry.getKey(), t);
}
return Optional.<Effect>empty();
})
.filter(Optional::isPresent)
.map(Optional::get)
.toList(), prepareExecutor);
return CompletableFuture.allOf(dietsLoadTask, effectsLoadTask).thenCompose(sync::whenPrepared).thenRunAsync(() -> {
PonyDiets.load(new PonyDiets(
dietsLoadTask.getNow(Map.of()),
effectsLoadTask.getNow(List.of())
));
}, applyExecutor);
}
private static CompletableFuture<Map<Identifier, JsonElement>> loadData(ResourceManager manager, Executor prepareExecutor, String path) {
return CompletableFuture.supplyAsync(() -> {
Map<Identifier, JsonElement> results = new HashMap<>();
JsonDataLoader.load(manager, path, Resources.GSON, results);
return results;
});
}
}

View file

@ -0,0 +1,76 @@
package com.minelittlepony.unicopia.diet;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.UseAction;
import net.minecraft.util.Util;
public record Effect(
List<TagKey<Item>> tags,
Optional<FoodComponent> foodComponent,
Ailment ailment
) implements Predicate<ItemStack> {
public static final Effect EMPTY = new Effect(List.of(), Optional.empty(), Ailment.EMPTY);
public static final Codec<Effect> CODEC = RecordCodecBuilder.create(instance -> instance.group(
TagKey.unprefixedCodec(RegistryKeys.ITEM).listOf().fieldOf("tags").forGetter(Effect::tags),
FoodAttributes.CODEC.optionalFieldOf("food_component").forGetter(Effect::foodComponent),
Ailment.CODEC.fieldOf("ailment").forGetter(Effect::ailment)
).apply(instance, Effect::new));
public Effect(PacketByteBuf buffer) {
this(buffer.readList(b -> TagKey.of(RegistryKeys.ITEM, b.readIdentifier())), buffer.readOptional(FoodAttributes::read), new Ailment(buffer));
}
public void afflict(Pony pony, ItemStack stack) {
ailment().effects().afflict(pony.asEntity(), stack);
}
public void appendTooltip(ItemStack stack, List<Text> tooltip, TooltipContext context) {
int size = tooltip.size();
tags.forEach(tag -> {
if (stack.isIn(tag)) {
tooltip.add(Text.literal(" ").append(Text.translatable(Util.createTranslationKey("tag", tag.id()))).formatted(Formatting.GRAY));
}
});
if (tooltip.size() == size) {
if (stack.isFood()) {
tooltip.add(Text.literal(" ").append(Text.translatable("tag.unicopia.food_types.fruits_and_vegetables")).formatted(Formatting.GRAY));
} else if (stack.getUseAction() == UseAction.DRINK) {
tooltip.add(Text.literal(" ").append(Text.translatable("tag.unicopia.food_types.drinks")).formatted(Formatting.GRAY));
}
}
if (context.isAdvanced() && stack.isFood()) {
if (!ailment().effects().isEmpty()) {
tooltip.add(Text.translatable("unicopia.diet.side_effects").formatted(Formatting.DARK_PURPLE));
ailment().effects().appendTooltip(tooltip);
}
}
}
public void toBuffer(PacketByteBuf buffer) {
buffer.writeCollection(tags, (b, t) -> b.writeIdentifier(t.id()));
buffer.writeOptional(foodComponent, FoodAttributes::write);
ailment.toBuffer(buffer);
}
@Override
public boolean test(ItemStack stack) {
return tags.stream().anyMatch(stack::isIn);
}
}

View file

@ -0,0 +1,60 @@
package com.minelittlepony.unicopia.diet;
import java.util.List;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.item.FoodComponent;
import net.minecraft.network.PacketByteBuf;
final class FoodAttributes {
static final Codec<FoodComponent> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("hunger").forGetter(FoodComponent::getHunger),
Codec.FLOAT.fieldOf("saturation").forGetter(FoodComponent::getSaturationModifier),
Codec.BOOL.optionalFieldOf("petFood", false).forGetter(FoodComponent::isMeat),
Codec.BOOL.optionalFieldOf("fastFood", false).forGetter(FoodComponent::isAlwaysEdible),
Codec.BOOL.optionalFieldOf("eatenQuickly", false).forGetter(FoodComponent::isSnack)
).apply(instance, FoodAttributes::create));
static FoodComponent create(int hunger, float saturation, boolean petFood, boolean fastFood, boolean eatenQuickly) {
return create(hunger, saturation, petFood, fastFood, eatenQuickly, List.of()).build();
}
static FoodComponent.Builder create(int hunger, float saturation, boolean petFood, boolean fastFood, boolean eatenQuickly, List<Pair<StatusEffectInstance, Float>> effects) {
var builder = new FoodComponent.Builder()
.hunger(hunger)
.saturationModifier(saturation);
if (petFood) {
builder.meat();
}
if (fastFood) {
builder.alwaysEdible();
}
if (eatenQuickly) {
builder.snack();
}
for (var effect : effects) {
builder.statusEffect(effect.getFirst(), effect.getSecond());
}
return builder;
}
static FoodComponent.Builder copy(FoodComponent food) {
return create(food.getHunger(), food.getSaturationModifier(), food.isMeat(), food.isAlwaysEdible(), food.isSnack(), food.getStatusEffects());
}
static FoodComponent read(PacketByteBuf buffer) {
return create(buffer.readInt(), buffer.readFloat(), buffer.readBoolean(), buffer.readBoolean(), buffer.readBoolean());
}
static void write(PacketByteBuf buffer, FoodComponent food) {
buffer.writeInt(food.getHunger());
buffer.writeFloat(food.getSaturationModifier());
buffer.writeBoolean(food.isMeat());
buffer.writeBoolean(food.isAlwaysEdible());
buffer.writeBoolean(food.isSnack());
}
}

View file

@ -0,0 +1,109 @@
package com.minelittlepony.unicopia.diet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.entity.effect.FoodPoisoningStatusEffect;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.ItemDuck;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;
public class PonyDiets implements DietView {
private final Map<Race, DietProfile> diets;
private final List<Effect> effects;
private static PonyDiets INSTANCE = new PonyDiets(Map.of(), List.of());
public static PonyDiets getInstance() {
return INSTANCE;
}
public static void load(PonyDiets diets) {
INSTANCE = diets;
}
PonyDiets(Map<Race, DietProfile> diets, List<Effect> effects) {
this.diets = diets;
this.effects = effects;
}
public PonyDiets(PacketByteBuf buffer) {
this(buffer.readMap(b -> b.readRegistryValue(Race.REGISTRY), DietProfile::new), buffer.readList(Effect::new));
}
public void toBuffer(PacketByteBuf buffer) {
buffer.writeMap(diets, (b, r) -> b.writeRegistryValue(Race.REGISTRY, r), (b, e) -> e.toBuffer(b));
buffer.writeCollection(effects, (b, e) -> e.toBuffer(b));
}
private DietProfile getDiet(Pony pony) {
return Optional.ofNullable(diets.get(pony.getObservedSpecies())).orElse(DietProfile.EMPTY);
}
private Effect getEffects(ItemStack stack) {
return effects.stream().filter(effect -> effect.test(stack)).findFirst().orElse(Effect.EMPTY);
}
private Effect getEffects(ItemStack stack, Pony pony) {
return getDiet(pony).findEffect(stack).orElseGet(() -> getEffects(stack));
}
@Override
public TypedActionResult<ItemStack> startUsing(ItemStack stack, World world, PlayerEntity user, Hand hand) {
return initEdibility(stack, user)
? FoodPoisoningStatusEffect.apply(stack, user)
: TypedActionResult.fail(stack);
}
@Override
public void finishUsing(ItemStack stack, World world, LivingEntity entity) {
if (initEdibility(stack, entity)) {
Pony.of(entity).ifPresent(pony -> getEffects(stack, pony).afflict(pony, stack));
}
}
@Override
public void appendTooltip(ItemStack stack, @Nullable PlayerEntity user, List<Text> tooltip, TooltipContext context) {
if (initEdibility(stack, user)) {
Pony pony = Pony.of(user);
tooltip.add(Text.translatable("unicopia.diet.information").formatted(Formatting.DARK_PURPLE));
getEffects(stack, pony).appendTooltip(stack, tooltip, context);
getDiet(pony).appendTooltip(stack, user, tooltip, context);
}
}
private boolean initEdibility(ItemStack stack, LivingEntity user) {
ItemDuck item = (ItemDuck)stack.getItem();
item.resetFoodComponent();
return Pony.of(user).filter(pony -> {
DietProfile diet = getDiet(pony);
if (!stack.isFood() && pony.getObservedSpecies().hasIronGut()) {
diet.findEffect(stack)
.flatMap(Effect::foodComponent)
.or(() -> getEffects(stack).foodComponent())
.ifPresent(item::setFoodComponent);
}
if (stack.isFood()) {
item.setFoodComponent(diet.getAdjustedFoodComponent(stack));
}
return true;
}).isPresent();
}
}

View file

@ -0,0 +1,69 @@
package com.minelittlepony.unicopia.diet.affliction;
import java.util.List;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.dynamic.Codecs;
public interface Affliction {
Affliction EMPTY = new Affliction() {
@Override
public void afflict(PlayerEntity player, ItemStack stack) { }
@Override
public AfflictionType<?> getType() {
return AfflictionType.EMPTY;
}
@Override
public void toBuffer(PacketByteBuf buffer) { }
};
Codec<Affliction> CODEC = Codecs.xor(AfflictionType.CODEC, Codec.list(AfflictionType.CODEC).xmap(
afflictions -> {
afflictions = afflictions.stream().filter(f -> !f.isEmpty()).toList();
return switch (afflictions.size()) {
case 0 -> EMPTY;
case 1 -> afflictions.get(0);
default -> new CompoundAffliction(afflictions);
};
},
affliction -> ((CompoundAffliction)affliction).afflictions
)).xmap(
either -> either.left().or(either::right).get(),
affliction -> affliction instanceof CompoundAffliction ? Either.left(affliction) : Either.right(affliction)
);
void afflict(PlayerEntity player, ItemStack stack);
default boolean isEmpty() {
return getType() == AfflictionType.EMPTY;
}
default void appendTooltip(List<Text> tooltip) {
tooltip.add(Text.literal(" ").append(getName()).formatted(Formatting.DARK_GRAY));
}
default Text getName() {
return Text.translatable(getType().getTranslationKey());
}
AfflictionType<?> getType();
void toBuffer(PacketByteBuf buffer);
static void write(PacketByteBuf buffer, Affliction affliction) {
buffer.writeIdentifier(affliction.getType().id());
affliction.toBuffer(buffer);
}
static Affliction read(PacketByteBuf buffer) {
return AfflictionType.REGISTRY.get(buffer.readIdentifier()).reader().apply(buffer);
}
}

View file

@ -0,0 +1,52 @@
package com.minelittlepony.unicopia.diet.affliction;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.RegistryUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import net.minecraft.network.PacketByteBuf.PacketReader;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Util;
import net.minecraft.util.dynamic.Codecs;
public record AfflictionType<T extends Affliction>(Codec<T> codec, Identifier id, PacketReader<T> reader) {
public static final String DEFAULT_ID = "unicopia:apply_status_effect";
public static final Registry<AfflictionType<?>> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("affliction_type"), DEFAULT_ID);
@SuppressWarnings("unchecked")
public static final Codec<Affliction> CODEC = Codecs.JSON_ELEMENT.<Affliction>flatXmap(json -> {
if (!json.isJsonObject()) {
return DataResult.error(() -> "Not a JSON object");
}
return Identifier.validate(JsonHelper.getString(JsonHelper.asObject(json, "affliction"), "type", AfflictionType.DEFAULT_ID))
.flatMap(type -> AfflictionType.REGISTRY.get(type).codec().parse(JsonOps.INSTANCE, json));
}, thing -> {
AfflictionType<?> type = thing.getType();
return ((Codec<Affliction>)type.codec()).encodeStart(JsonOps.INSTANCE, thing).map(json -> {
if (json.isJsonObject()) {
json.getAsJsonObject().addProperty("type", type.id().toString());
}
return json;
});
});
public static final AfflictionType<Affliction> EMPTY = register("empty", Codec.unit(Affliction.EMPTY), buffer -> Affliction.EMPTY);
public static final AfflictionType<Affliction> MANY = register("many", CompoundAffliction.CODEC, CompoundAffliction::new);
public static final AfflictionType<StatusEffectAffliction> APPLY_STATUS_EFFECT = register("apply_status_effect", StatusEffectAffliction.CODEC, StatusEffectAffliction::new);
public static final AfflictionType<LoseHungerAffliction> LOSE_HUNGER = register("lose_hunger", LoseHungerAffliction.CODEC, LoseHungerAffliction::new);
public static final AfflictionType<HealingAffliction> HEALING = register("healing", HealingAffliction.CODEC, HealingAffliction::new);
public static final AfflictionType<ClearLoveSicknessAffliction> CURE_LOVE_SICKNESS = register("cure_love_sickness", ClearLoveSicknessAffliction.CODEC, buffer -> ClearLoveSicknessAffliction.INSTANCE);
static <T extends Affliction> AfflictionType<T> register(String name, Codec<T> codec, PacketReader<T> reader) {
return Registry.register(REGISTRY, Unicopia.id(name), new AfflictionType<>(codec, Unicopia.id(name), reader));
}
public String getTranslationKey() {
return Util.createTranslationKey("affliction", id());
}
public static void bootstrap() { }
}

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