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;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.registry.Registries;
import net.minecraft.world.World;
public interface Debug {
@ -18,6 +20,16 @@ public interface Debug {
}
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 {
for (var type : BoatEntity.Type.values()) {
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_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_SEAPONY = IS_PLAYER.and(raceMatches(Race::isFish));
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));

View file

@ -24,37 +24,44 @@ import net.minecraft.util.Identifier;
import net.minecraft.registry.Registry;
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 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();
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) {
return register(Unicopia.id(name), magic, flight, earth, nocturnal, canHang);
public static Race register(String name, Availability availability, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean 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) {
return Registry.register(REGISTRY, id, new Race(Suppliers.memoize(() -> new Composite(REGISTRY.get(id), null)), magic, flight, earth, nocturnal, canHang));
public static Race register(Identifier id, Availability availability, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean 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() {
return RegistryKeyArgumentType.registryKey(REGISTRY_KEY);
return RegistryKeyArgumentType.registryKey(COMMAND_REGISTRY.getKey());
}
/**
* The default, unset race.
* 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 HUMAN = register("human", false, FlightType.NONE, false, false, false);
public static final Race EARTH = register("earth", false, FlightType.NONE, true, false, false);
public static final Race UNICORN = register("unicorn", true, FlightType.NONE, false, false, false);
public static final Race PEGASUS = register("pegasus", false, FlightType.AVIAN, false, false, false);
public static final Race BAT = register("bat", false, FlightType.AVIAN, false, true, true);
public static final Race ALICORN = register("alicorn", true, FlightType.AVIAN, true, false, false);
public static final Race CHANGELING = register("changeling", false, FlightType.INSECTOID, false, false, true);
public static final Race KIRIN = register("kirin", true, 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", Availability.COMMANDS, false, FlightType.NONE, false, false, false);
public static final Race EARTH = register("earth", Availability.DEFAULT, false, FlightType.NONE, true, false, false);
public static final Race UNICORN = register("unicorn", Availability.DEFAULT, true, FlightType.NONE, 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", Availability.DEFAULT, false, FlightType.AVIAN, false, true, true);
public static final Race ALICORN = register("alicorn", Availability.COMMANDS, true, FlightType.AVIAN, true, false, false);
public static final Race CHANGELING = register("changeling", Availability.DEFAULT, false, FlightType.INSECTOID, false, false, true);
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() {}
@ -62,8 +69,8 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return compositeSupplier.get();
}
public Composite composite(@Nullable Race pseudo) {
return pseudo == null ? composite() : new Composite(this, pseudo);
public Composite composite(@Nullable Race pseudo, @Nullable Race potential) {
return pseudo == null && potential == null ? composite() : new Composite(this, pseudo, potential);
}
@Override
@ -83,6 +90,10 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return !isHuman();
}
public boolean isFish() {
return this == SEAPONY;
}
public boolean isHuman() {
return this == UNSET || this == HUMAN;
}
@ -91,16 +102,12 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return !isNocturnal();
}
public boolean isOp() {
return this == ALICORN;
}
public boolean canFly() {
return !flightType().isGrounded();
}
public boolean canInteractWithClouds() {
return canFly() && this != CHANGELING && this != BAT;
return canFly() && this != CHANGELING && this != BAT && this != HIPPOGRIFF;
}
public Identifier getId() {
@ -145,6 +152,10 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
return this;
}
public Race or(Race other) {
return isEquine() ? this : other;
}
@Override
public int 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());
}
public record Composite (Race physical, @Nullable Race pseudo) {
public record Composite (Race physical, @Nullable Race pseudo, @Nullable Race potential) {
public Race collapsed() {
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_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_PEGASUS_WINGSFLAP = register("entity.player.pegasus.wingsflap");
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_KIRIN_RAGE = ENTITY_POLAR_BEAR_WARNING;
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_HEARTBEAT = register("entity.player.heartbeat");
@ -146,6 +149,11 @@ public interface USounds {
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 final class Vanilla extends SoundEvents {}

View file

@ -1,7 +1,5 @@
package com.minelittlepony.unicopia;
import com.minelittlepony.unicopia.item.toxin.Toxics;
import net.minecraft.block.Block;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageType;
@ -27,6 +25,7 @@ public interface UTags {
TagKey<Item> SPOOKED_MOB_DROPS = item("spooked_mob_drops");
TagKey<Item> IS_DELIVERED_AGGRESSIVELY = item("is_delivered_aggressively");
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> HORSE_SHOES = item("horse_shoes");
@ -78,8 +77,4 @@ public interface UTags {
static TagKey<DimensionType> dimension(String 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.container.SpellbookChapterLoader;
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.effect.UPotions;
import com.minelittlepony.unicopia.entity.mob.UEntities;
@ -64,7 +66,6 @@ public class Unicopia implements ModInitializer {
@Override
public void onInitialize() {
Channel.bootstrap();
UTags.bootstrap();
UCriteria.bootstrap();
UEntities.bootstrap();
Commands.bootstrap();
@ -83,20 +84,18 @@ public class Unicopia implements ModInitializer {
});
NocturnalSleepManager.bootstrap();
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE);
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);
registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA));
UGameEvents.bootstrap();
UBlocks.bootstrap();
UPOIs.bootstrap();
UItems.bootstrap();
UPotions.bootstrap();
UParticles.bootstrap();
USounds.bootstrap();
Race.bootstrap();
SpellType.bootstrap();
AfflictionType.bootstrap();
Abilities.bootstrap();
UScreenHandlers.bootstrap();
UWorldGen.bootstrap();
@ -104,6 +103,15 @@ public class Unicopia implements ModInitializer {
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 {
Optional<Pony> getPony();

View file

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

View file

@ -24,6 +24,9 @@ public interface Abilities {
.toList();
});
// all races
Ability<?> CHANGE_FORM = register(new ChangeFormAbility(), "change_form", AbilitySlot.PRIMARY);
// unicorn / alicorn
Ability<?> CAST = register(new UnicornCastingAbility(), "cast", 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<?> 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<?> TOGGLE_FLIGHT = register(new PegasusFlightToggleAbility(), "toggle_flight", AbilitySlot.TERTIARY);
Ability<?> TOGGLE_FLIGHT = register(new ToggleFlightAbility(), "toggle_flight", AbilitySlot.TERTIARY);
// changeling
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<?> 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) {
Identifier id = Unicopia.id(name);
BY_SLOT.computeIfAbsent(slot, s -> new LinkedHashSet<>()).add(power);

View file

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

View file

@ -1,55 +1,20 @@
package com.minelittlepony.unicopia.ability;
import java.util.Optional;
import com.minelittlepony.unicopia.AwaitTickQueue;
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;
/**
* A magic casting ability for unicorns.
* (only shields for now)
* An ability to screeeeeeeeEeEeEeeee!
*/
public class BatEeeeAbility implements Ability<Numeric> {
public class BatEeeeAbility extends ScreechAbility {
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
public boolean canUse(Race race) {
@ -57,22 +22,7 @@ public class BatEeeeAbility implements Ability<Numeric> {
}
@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;
protected void playSounds(Pony player, Random rng, float strength) {
int count = 1 + rng.nextInt(10) + (int)(strength * 10);
for (int i = 0; i < count; i++) {
@ -81,6 +31,7 @@ public class BatEeeeAbility implements Ability<Numeric> {
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 k = 0; k < count; k++) {
AwaitTickQueue.scheduleTask(player.asWorld(), w -> {
@ -88,59 +39,14 @@ public class BatEeeeAbility implements Ability<Numeric> {
(0.9F + (rng.nextFloat() - 0.5F) / 2F) * strength,
1.6F + (rng.nextFloat() - 0.5F)
);
player.asWorld().emitGameEvent(player.asEntity(), GameEvent.ENTITY_ACTION, player.asEntity().getEyePos());
}, 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) {
player.asEntity().damage(player.damageOf(UDamageTypes.BAT_SCREECH, player), 0.1F);
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
public Optional<Multi> prepare(Pony player) {
if (player.isHanging()) {
if (player.getAcrobatics().isHanging()) {
return Optional.of(new Multi(BlockPos.ZERO, 0));
}
return TraceHelper.findBlock(player.asEntity(), 5, 1)
.map(BlockPos::down)
.filter(player::canHangAt)
.filter(player.getAcrobatics()::canHangAt)
.map(pos -> new Multi(pos, 1));
}
@ -55,13 +55,13 @@ public class BatPonyHangAbility implements Ability<Multi> {
@Override
public boolean apply(Pony player, Multi data) {
if (data.hitType() == 0 && player.isHanging()) {
player.stopHanging();
if (data.hitType() == 0 && player.getAcrobatics().isHanging()) {
player.getAcrobatics().stopHanging();
return true;
}
if (data.hitType() == 1 && player.canHangAt(data.pos().pos())) {
player.startHanging(data.pos().pos());
if (data.hitType() == 1 && player.getAcrobatics().canHangAt(data.pos().pos())) {
player.getAcrobatics().startHanging(data.pos().pos());
}
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.CastingMethod;
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.mixin.MixinFallingBlockEntity;
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);
iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true)
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE))
.setDisguise(looked);
Disguise currentDisguise = iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true)
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE));
if (currentDisguise.isOf(looked)) {
looked = null;
}
currentDisguise.setDisguise(looked);
if (!player.isCreative()) {
iplayer.getMagicalReserves().getMana().multiply(0.1F);
@ -65,7 +70,7 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
@Override
public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getEnergy().add(20);
player.getMagicalReserves().getEnergy().add(2F);
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.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.TraceHelper;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.item.BoneMealItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
@ -71,8 +73,21 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
ItemStack stack = new ItemStack(Items.BONE_MEAL);
if (BoneMealItem.useOnFertilizable(stack, w, pos)
|| BoneMealItem.useOnGround(stack, w, pos, Direction.UP)) {
if (state.getBlock() instanceof Growable growable) {
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;
}
@ -92,4 +107,8 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
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
public Identifier getIcon(Pony player) {
Identifier id = Abilities.REGISTRY.getId(this);
return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath()
return getId().withPath(p -> "textures/gui/ability/" + p
+ "_" + player.getObservedSpecies().getId().getPath()
+ "_" + (getKickDirection(player) > 0 ? "forward" : "backward")
+ ".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;
public class PegasusFlightToggleAbility implements Ability<Hit> {
public class ToggleFlightAbility implements Ability<Hit> {
@Override
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.magic.Caster;
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.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -163,16 +164,19 @@ public class UnicornTeleportAbility implements Ability<Pos> {
Vec3d dest = destination.vec().add(offset);
participant.teleport(
dest.x,
getTargetYPosition(participant.getEntityWorld(), BlockPos.ofFloored(dest), ShapeContext.of(participant)),
dest.z
);
dest = new Vec3d(dest.x, getTargetYPosition(participant.getEntityWorld(), BlockPos.ofFloored(dest), ShapeContext.of(participant)), dest.z);
participant.teleport(dest.x, dest.y, dest.z);
teleporter.subtractEnergyCost(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;
}

View file

@ -30,11 +30,17 @@ public class RageAbilitySpell extends AbstractSpell {
private int age;
private int ticksExtenguishing;
private int ticksToExtenguish;
public RageAbilitySpell(CustomisedSpellType<?> type) {
super(type);
setHidden(true);
}
public void setExtenguishing() {
ticksToExtenguish += 15;
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
@ -42,7 +48,7 @@ public class RageAbilitySpell extends AbstractSpell {
return false;
}
if (source.asEntity().isInsideWaterOrBubbleColumn()) {
if (source.asEntity().isInsideWaterOrBubbleColumn() || source.asEntity().isFrozen() || ticksToExtenguish > 0) {
ticksExtenguishing++;
source.playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 1);
source.spawnParticles(ParticleTypes.CLOUD, 12);
@ -51,6 +57,10 @@ public class RageAbilitySpell extends AbstractSpell {
ticksExtenguishing = 0;
}
if (ticksToExtenguish > 0) {
ticksToExtenguish--;
}
if (ticksExtenguishing > 10) {
return false;
}
@ -94,7 +104,7 @@ public class RageAbilitySpell extends AbstractSpell {
}
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());
}
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) {
REGISTRY = new HashMap<>(newRegistry);
REGISTRY = newRegistry;
ITEMS.clear();
REGISTRY.forEach((itemId, traits) -> {
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) {
return REGISTRY.entrySet().stream()
.filter(e -> e.getValue().get(trait) > 0)
.map(Map.Entry::getKey)
.flatMap(id -> Registries.ITEM.getOrEmpty(id).stream());
return ITEMS.getOrDefault(trait, List.of()).stream();
}
public static Optional<SpellTraits> getEmbeddedTraits(ItemStack stack) {

View file

@ -5,7 +5,9 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@ -26,7 +28,11 @@ import net.minecraft.resource.SinglePreparationResourceReloader;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
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.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Identifier, TraitLoader.TraitStream>> implements IdentifiableResourceReloadListener {
private static final Identifier ID = Unicopia.id("data/traits");
@ -77,9 +83,24 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
@Override
protected void apply(Multimap<Identifier, TraitStream> prepared, ResourceManager manager, Profiler profiler) {
profiler.startTick();
SpellTraits.load(prepared.values().stream()
Set<Map.Entry<TraitStream.Key, SpellTraits>> newRegistry = prepared.values().stream()
.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();
}
@ -88,14 +109,14 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
boolean replace();
Stream<Map.Entry<Identifier, SpellTraits>> entries();
Stream<Map.Entry<Key, SpellTraits>> entries();
static TraitStream of(Identifier id, String pack, JsonObject json) {
if (json.has("items") && json.get("items").isJsonObject()) {
return new TraitMap(JsonHelper.getBoolean(json, "replace", false),
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)
))
);
@ -106,23 +127,16 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
SpellTraits.fromString(JsonHelper.getString(json, "traits")).orElse(SpellTraits.EMPTY),
StreamSupport.stream(JsonHelper.getArray(json, "items").spliterator(), false)
.map(JsonElement::getAsString)
.map(Identifier::tryParse)
.filter(item -> {
if (item == null || !Registries.ITEM.containsId(item)) {
Unicopia.LOGGER.warn("Skipping unknown item {} in {}:{}", item, pack, id);
return false;
}
return true;
})
.map(Key::of)
.collect(Collectors.toSet())
);
}
record TraitMap (
boolean replace,
Map<Identifier, SpellTraits> items) implements TraitStream {
Map<Key, SpellTraits> items) implements TraitStream {
@Override
public Stream<Entry<Identifier, SpellTraits>> entries() {
public Stream<Entry<Key, SpellTraits>> entries() {
return items.entrySet().stream();
}
}
@ -130,12 +144,32 @@ public class TraitLoader extends SinglePreparationResourceReloader<Multimap<Iden
record TraitSet (
boolean replace,
SpellTraits traits,
Set<Identifier> items) implements TraitStream {
Set<Key> items) implements TraitStream {
@Override
public Stream<Entry<Identifier, SpellTraits>> entries() {
public Stream<Entry<Key, SpellTraits>> entries() {
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 SPLIT_SEA = CUSTOM_EVENT.createTrigger("split_sea");
CustomEventCriterion.Trigger RIDE_BALLOON = CUSTOM_EVENT.createTrigger("ride_balloon");
CustomEventCriterion.Trigger TELEPORT_ABOVE_WORLD = CUSTOM_EVENT.createTrigger("teleport_above_world");
static void bootstrap() { }
}

View file

@ -41,6 +41,10 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
@Override
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) {
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
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) {
super.scheduledTick(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) {

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;
import java.util.Arrays;
import java.util.Locale;
import org.joml.Vector3f;
import com.minelittlepony.unicopia.USounds;
import net.minecraft.block.Block;
@ -14,11 +17,14 @@ import net.minecraft.entity.mob.SlimeEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.particle.DustParticleEffect;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
@ -34,24 +40,29 @@ import net.minecraft.world.WorldAccess;
import net.minecraft.world.WorldView;
public class SlimePustuleBlock extends Block {
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);
static final VoxelShape DRIP_SHAPE = VoxelShapes.union(
private static final EnumProperty<Shape> SHAPE = EnumProperty.of("shape", Shape.class);
private static final BooleanProperty POWERED = Properties.POWERED;
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(3, 15, 4, 9, 16, 10),
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(5, 10, 5, 11, 13, 11),
Block.createCuboidShape(6, 13, 6, 10, 15, 10),
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) {
super(settings.ticksRandomly());
setDefaultState(getDefaultState().with(SHAPE, Shape.DRIP));
setDefaultState(getDefaultState().with(SHAPE, Shape.DRIP).with(POWERED, false));
}
@Override
@ -68,6 +79,17 @@ public class SlimePustuleBlock extends Block {
public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random 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) {
VoxelShape shape = state.getCullingShape(world, pos);
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
@Override
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);
slime.setSize(1, true);
slime.setPosition(pos.toCenterPos());
@ -132,7 +154,7 @@ public class SlimePustuleBlock extends Block {
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(SHAPE);
builder.add(SHAPE, POWERED);
}
@Deprecated
@ -154,20 +176,66 @@ public class SlimePustuleBlock extends Block {
Shape currentShape = state.get(SHAPE);
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);
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
public BlockState getPlacementState(ItemPlacementContext ctx) {
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) {

View file

@ -11,8 +11,8 @@ import net.minecraft.world.WorldAccess;
public class StableDoorBlock extends DoorBlock {
public StableDoorBlock(Settings settings) {
super(settings, BlockSetType.OAK);
public StableDoorBlock(Settings settings, BlockSetType blockSet) {
super(settings, blockSet);
}
@Override
@ -21,6 +21,12 @@ public class StableDoorBlock extends DoorBlock {
if (direction.getAxis() == Direction.Axis.Y && half == DoubleBlockHalf.LOWER == (direction == Direction.UP)) {
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;
}

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;
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.BlockEntityType;
import net.minecraft.block.entity.BlockEntityType.Builder;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.registry.Registry;
import net.minecraft.registry.Registries;
public interface UBlockEntities {
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) {
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.CompactedCloudBlock;
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.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.SoggyCloudSlabBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock;
import com.minelittlepony.unicopia.item.UItems;
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_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 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);
@ -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 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,
() -> UBlocks.SOGGY_CLOUD,
() -> 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_STAIRS = register("cloud_stairs", new CloudStairsBlock(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,
PoreousCloudStairsBlock CLOUD_STAIRS = register("cloud_stairs", new PoreousCloudStairsBlock(CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.SOGGY_CLOUD_STAIRS), ItemGroups.NATURAL);
Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false,
null,
() -> 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 CLOUD_PLANKS_STAIRS = register("cloud_planks_stairs", new CloudStairsBlock(CLOUD_PLANKS.getDefaultState(), Settings.copy(CLOUD_PLANKS), null), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(Settings.copy(CLOUD_PLANKS)));
Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(CLOUD_PLANKS.getDefaultState()));
Block CLOUD_PLANK_SLAB = register("cloud_plank_slab", new CloudSlabBlock(Settings.copy(CLOUD_PLANKS), false, null), ItemGroups.BUILDING_BLOCKS);
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);
SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(Settings.copy(CLOUD), () -> UBlocks.CLOUD));
SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(Settings.copy(CLOUD), () -> UBlocks.CLOUD_SLAB));
CloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new CloudStairsBlock(SOGGY_CLOUD.getDefaultState(), Settings.copy(CLOUD), null));
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 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 CLOUD_PILLAR = register("cloud_pillar", new CloudPillarBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.NATURAL);
Block CLOUD_BED = register("cloud_bed", new CloudBedBlock(CLOUD.getDefaultState(), Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOL)));
Block CLOUD_PILLAR = register("cloud_pillar", new CloudPillarBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid()), ItemGroups.NATURAL);
Block CLOUD_CHEST = register("cloud_chest", new CloudChestBlock(Settings.copy(DENSE_CLOUD).instrument(Instrument.BASS).strength(2.5f), DENSE_CLOUD.getDefaultState()), ItemGroups.FUNCTIONAL);
Block CLOTH_BED = register("cloth_bed", new FancyBedBlock("cloth", Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOD)));
Block CLOUD_BED = register("cloud_bed", new CloudBedBlock("cloud", CLOUD.getDefaultState(), Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOL)));
Block CLAM_SHELL = register("clam_shell", new ShellsBlock(Settings.create().mapColor(MapColor.DULL_PINK).breakInstantly().nonOpaque()));
Block SCALLOP_SHELL = register("scallop_shell", new ShellsBlock(Settings.create().mapColor(MapColor.DULL_PINK).breakInstantly().nonOpaque()));
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) {
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) {
ItemGroupRegistry.register(id,
CloudBlock.isCloudBlock(block) ? new CloudBlockItem(block, new Item.Settings()) : new BlockItem(block, new Item.Settings()
), group);
ItemGroupRegistry.register(id, block instanceof CloudLike ? new CloudBlockItem(block, new Item.Settings()) : new BlockItem(block, new Item.Settings()), group);
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) {
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);
}
return Registry.register(Registries.BLOCK, id, block);
@ -199,7 +240,7 @@ public interface UBlocks {
StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG);
StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_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);
FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60);

View file

@ -1,25 +1,34 @@
package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
import com.terraformersmc.terraform.boat.api.TerraformBoatType;
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.WoodTypeBuilder;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.WoodType;
import net.minecraft.registry.RegistryKey;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.Identifier;
public interface UWoodTypes {
WoodType PALM = register("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) {
Identifier id = Unicopia.id(name);
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 net.minecraft.block.*;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
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);
ZapAppleLeavesBlock() {
setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.HIBERNATING));
setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.GREENING));
}
@Override
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.FLOWERING_ZAP_LEAVES)
|| 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
protected ZapAppleStageStore.Stage getStage(BlockState state) {
return state.get(STAGE);

View file

@ -3,20 +3,14 @@ package com.minelittlepony.unicopia.block.cloud;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.UBlockEntities;
import net.minecraft.block.BedBlock;
import com.minelittlepony.unicopia.block.FancyBedBlock;
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.entity.Entity;
import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.util.ActionResult;
import net.minecraft.util.DyeColor;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
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.World;
public class CloudBedBlock extends BedBlock {
public class CloudBedBlock extends FancyBedBlock implements CloudLike {
private final BlockState baseState;
private final CloudBlock baseBlock;
public CloudBedBlock(BlockState baseState, Settings settings) {
super(DyeColor.WHITE, settings);
public CloudBedBlock(String base, BlockState baseState, Settings settings) {
super(base, settings);
this.baseState = baseState;
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) {
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.World;
public class CloudBlock extends Block {
public static boolean isCloudBlock(Block block) {
return block instanceof CloudBlock || block instanceof CloudStairsBlock || block instanceof CloudBedBlock;
}
public class CloudBlock extends Block implements CloudLike {
protected final boolean meltable;
public CloudBlock(Settings settings, boolean meltable) {
@ -59,7 +55,7 @@ public class CloudBlock extends Block {
entity.handleFallDamage(fallDistance, 0, world.getDamageSources().fall());
generateSurfaceParticles(world, state, pos, ShapeContext.absent(), 9);
if (fallDistance > 7) {
if (!world.isClient && fallDistance > 7) {
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;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
@ -18,15 +16,13 @@ import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
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 @Nullable Supplier<Soakable> soggyBlock;
public CloudStairsBlock(BlockState baseState, Settings settings, @Nullable Supplier<Soakable> soggyBlock) {
public CloudStairsBlock(BlockState baseState, Settings settings) {
super(baseState, settings);
this.baseBlock = (CloudBlock)baseState.getBlock();
this.soggyBlock = soggyBlock;
}
@Override
@ -92,15 +88,4 @@ public class CloudStairsBlock extends StairsBlock implements Soakable {
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) {
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.Property;
import net.minecraft.util.ActionResult;
import net.minecraft.util.BlockMirror;
import net.minecraft.util.BlockRotation;
import net.minecraft.util.Hand;
import net.minecraft.util.Util;
import net.minecraft.util.hit.BlockHitResult;
@ -43,13 +45,21 @@ public class CompactedCloudBlock extends CloudBlock {
);
});
public CompactedCloudBlock(Settings settings) {
super(settings, true);
private final BlockState baseState;
public CompactedCloudBlock(BlockState baseState) {
super(Settings.copy(baseState.getBlock()).dropsLike(baseState.getBlock()), true);
this.baseState = baseState;
PROPERTIES.forEach(property -> {
setDefaultState(getDefaultState().with(property, true));
});
}
@Override
public ItemStack getPickStack(BlockView world, BlockPos pos, BlockState state) {
return baseState.getBlock().getPickStack(world, pos, baseState);
}
@Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context, EquineContext equineContext) {
return SHAPE_CACHE.apply(state);
@ -76,4 +86,24 @@ public class CompactedCloudBlock extends CloudBlock {
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
@Override
public BlockState getSoggyState(int moisture) {
return soggyBlock == null ? null : soggyBlock.get().getSoggyState(moisture);
}
@Override
public int getMoisture(BlockState state) {
return 0;
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState());
}
return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture);
}
@Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (soggyBlock != null && world.hasRain(pos) && world.isAir(pos.up())) {
world.setBlockState(pos, Soakable.copyProperties(state, soggyBlock.get().getSoggyState(random.nextBetween(1, 5))));
return;
if (state.getBlock() instanceof Soakable soakable && world.hasRain(pos) && world.isAir(pos.up())) {
@Nullable
BlockState soggyState = soakable.getStateWithMoisture(state, random.nextBetween(1, 5));
if (soggyState != null) {
world.setBlockState(pos, soggyState);
return;
}
}
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 net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
@ -31,9 +30,12 @@ public interface Soakable {
IntProperty MOISTURE = IntProperty.of("moisture", 1, 7);
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) {
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) {
ItemStack stack = player.getStackInHand(hand);
if (stack.getItem() == Items.GLASS_BOTTLE) {
if (!player.isCreative()) {
stack.split(1);
}
if (stack.isEmpty()) {
player.setStackInHand(hand, 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);
updateMoisture(dryBlock, state, world, pos, state.get(MOISTURE) - 1);
static ActionResult tryCollectMoisture(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (state.getBlock() instanceof Soakable soakable) {
ItemStack stack = player.getStackInHand(hand);
if (stack.getItem() == Items.GLASS_BOTTLE) {
if (!player.isCreative()) {
stack.split(1);
}
if (stack.isEmpty()) {
player.setStackInHand(hand, 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);
updateMoisture(soakable, state, world, pos, soakable.getMoisture(state) - 1);
return ActionResult.SUCCESS;
return ActionResult.SUCCESS;
}
}
return ActionResult.PASS;
}
static void tickMoisture(Block dryBlock, BlockState state, ServerWorld world, BlockPos pos, Random random) {
int moisture = state.get(MOISTURE);
static void tickMoisture(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (state.getBlock() instanceof Soakable soakable) {
int moisture = soakable.getMoisture(state);
if (world.hasRain(pos) && world.isAir(pos.up())) {
if (moisture < 7) {
world.setBlockState(pos, state.with(MOISTURE, moisture + 1));
}
} else {
if (moisture > 1) {
BlockPos neighborPos = pos.offset(Util.getRandom(Soakable.DIRECTIONS, random));
BlockState neighborState = world.getBlockState(neighborPos);
if (world.hasRain(pos) && world.isAir(pos.up())) {
if (moisture < 7) {
world.setBlockState(pos, soakable.getStateWithMoisture(state, moisture + 1));
}
} else {
if (moisture > 1) {
BlockPos neighborPos = pos.offset(Util.getRandom(Soakable.DIRECTIONS, random));
BlockState neighborState = world.getBlockState(neighborPos);
if (neighborState.getBlock() instanceof Soakable soakable && soakable.getMoisture(neighborState) < moisture) {
int half = Math.max(1, moisture / 2);
@Nullable
BlockState newNeighborState = soakable.getSoggyState(half);
if (newNeighborState != null) {
updateMoisture(dryBlock, state, world, pos, moisture - half);
world.setBlockState(neighborPos, soakable.getSoggyState(half));
world.emitGameEvent(null, GameEvent.BLOCK_CHANGE, neighborPos);
return;
if (neighborState.getBlock() instanceof Soakable neighborSoakable && neighborSoakable.getMoisture(neighborState) < moisture) {
int half = Math.max(1, moisture / 2);
@Nullable
BlockState newNeighborState = neighborSoakable.getStateWithMoisture(neighborState, half);
if (newNeighborState != null) {
updateMoisture(soakable, state, world, pos, moisture - half);
world.setBlockState(neighborPos, soakable.getStateWithMoisture(state, half));
world.emitGameEvent(null, GameEvent.BLOCK_CHANGE, neighborPos);
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) {
if (newMoisture <= 0) {
world.setBlockState(pos, copyProperties(state, dryBlock.getDefaultState()));
} else {
world.setBlockState(pos, state.with(MOISTURE, newMoisture));
}
private static void updateMoisture(Soakable soakable, BlockState state, World world, BlockPos pos, int newMoisture) {
world.setBlockState(pos, soakable.getStateWithMoisture(state, newMoisture));
world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F));
}
@Nullable
@SuppressWarnings({ "rawtypes", "unchecked" })
static BlockState copyProperties(BlockState from, BlockState to) {
for (Property property : from.getProperties()) {
to = to.withIfExists(property, from.get(property));
static BlockState copyProperties(BlockState from, @Nullable BlockState to) {
if (to != null) {
for (Property property : from.getProperties()) {
to = to.withIfExists(property, from.get(property));
}
}
return to;
}

View file

@ -2,9 +2,12 @@ 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.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
@ -12,6 +15,7 @@ import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class SoggyCloudBlock extends CloudBlock implements Soakable {
@ -24,25 +28,30 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
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
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);
}
@Override
@Deprecated
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
@ -53,6 +62,6 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
@Override
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 org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
@ -12,6 +15,7 @@ import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class SoggyCloudSlabBlock extends CloudSlabBlock {
@ -24,26 +28,30 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
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
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);
}
@Override
@Deprecated
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
@ -54,6 +62,6 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
@Override
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);
if (world.isClient) {
return;
}
if (fallDistance > 3) {
world.breakBlock(pos, true);
return;

View file

@ -1,7 +1,12 @@
package com.minelittlepony.unicopia.client;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
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.CloudsEscapingParticle;
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.item.ChameleonItem;
import com.minelittlepony.unicopia.item.EnchantableItem;
import com.minelittlepony.unicopia.item.FancyBedItem;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBedItem;
import com.minelittlepony.unicopia.particle.UParticles;
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.PendingParticleFactory;
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.BlockState;
import net.minecraft.block.entity.BlockEntity;
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.FoliageColors;
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.item.ItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
public interface URenderers {
BlockEntity CHEST_RENDER_ENTITY = new CloudChestBlock.TileData(BlockPos.ORIGIN, UBlocks.CLOUD_CHEST.getDefaultState());
static void bootstrap() {
ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::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.LIGHTNING_BOLT, LightningBoltParticle::new);
AccessoryFeatureRenderer.register(BraceletFeatureRenderer::new);
AccessoryFeatureRenderer.register(AmuletFeatureRenderer::new);
AccessoryFeatureRenderer.register(WingsFeatureRenderer::new);
AccessoryFeatureRenderer.register(HornFeatureRenderer::new);
AccessoryFeatureRenderer.register(IcarusWingsFeatureRenderer::new);
AccessoryFeatureRenderer.register(BatWingsFeatureRenderer::new);
AccessoryFeatureRenderer.register(GlassesFeatureRenderer::new);
AccessoryFeatureRenderer.register(HeldEntityFeatureRenderer::new);
AccessoryFeatureRenderer.register(
BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new,
WingsFeatureRenderer::new, HornFeatureRenderer::new, IcarusWingsFeatureRenderer::new, BatWingsFeatureRenderer::new,
HeldEntityFeatureRenderer::new
);
EntityRendererRegistry.register(UEntities.THROWN_ITEM, 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.AIR_BALLOON, AirBalloonEntityRenderer::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.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);
BuiltinItemRendererRegistry.INSTANCE.register(UItems.FILLED_JAR, (stack, mode, matrices, vertices, light, 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();
});
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));
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);
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));
@ -190,6 +125,80 @@ public interface URenderers {
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) {
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.Unicopia;
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.spellbook.SpellbookScreen;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
@ -101,6 +102,7 @@ public class UnicopiaClient implements ClientModInitializer {
URenderers.bootstrap();
HandledScreens.register(UScreenHandlers.SPELL_BOOK, SpellbookScreen::new);
HandledScreens.register(UScreenHandlers.SHAPING_BENCH, ShapingBenchScreen::new);
ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
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();
for (Race race : Race.REGISTRY) {
if (!race.isUnset()) {
if (!race.isUnset() && race.availability().isGrantable()) {
Bounds bound = WHITELIST_GRID_PACKER.next();
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 {
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);
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);
int iconPosition = ((size - iconSize + slotPadding + 1) / 2);
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();

View file

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

View file

@ -41,10 +41,10 @@ public class TribeButton extends Button {
MinecraftClient mc = MinecraftClient.getInstance();
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);
if (hovered && screenWidth > 0) {
if (isFocused() && screenWidth > 0) {
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);
}
@ -59,7 +59,7 @@ public class TribeButton extends Button {
int foreColor = getStyle().getColor();
if (!active) {
foreColor = 10526880;
} else if (isHovered()) {
} else if (isSelected()) {
foreColor = 16777120;
}

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.client.gui;
import org.lwjgl.glfw.GLFW;
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.Label;
import com.minelittlepony.unicopia.Race;
@ -17,6 +18,8 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
private final BooleanConsumer callback;
private final ScrollContainer textBody = new ScrollContainer();
public TribeConfirmationScreen(BooleanConsumer callback, Race selection) {
super(Text.translatable("gui.unicopia.tribe_selection"));
this.callback = callback;
@ -25,12 +28,26 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
@Override
protected void init() {
final int columnHeight = 167;
final int columnWidth = 310;
final int padding = 15;
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))
.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 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);
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;
int maxWidth = 280;
int maxWidth = 270;
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());
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));
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;
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());
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));
top += block.getBounds().height;
}
@ -81,9 +100,7 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
}
@Override
public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) {
super.renderBackground(context, mouseX, mouseY, delta);
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
final int columnHeight = 180;
final int columnWidth = 310;
final int segmentWidth = 123;
@ -91,7 +108,7 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
int top = (height - columnHeight) / 2;
int left = (width - columnWidth) / 2;
top += 25;
top += 40;
final int zOffset = 0;
@ -106,10 +123,16 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
left = width / 2;
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);
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
@ -120,7 +143,11 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
callback.accept(false);
return true;
}
if (keyCode == GLFW.GLFW_KEY_ENTER) {
callback.accept(true);
return true;
}
return super.keyPressed(keyCode, scanCode, modifiers);

View file

@ -1,8 +1,11 @@
package com.minelittlepony.unicopia.client.gui;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.lwjgl.glfw.GLFW;
import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.element.Label;
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.MsgRequestSpeciesChange;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
public class TribeSelectionScreen extends GameGui implements HidesHud {
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 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) {
super(Text.translatable(baseString));
this.allowedRaces = allowedRaces;
@ -51,33 +64,26 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
top += block.getBounds().height;
top += 30;
final int itemWidth = 70 + 10;
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;
List<Race> options = Race.REGISTRY.stream().filter(race -> race.availability().isSelectable()).toList();
this.options.clear();
for (Race race : options) {
addOption(race, x + (column * itemWidth), y + (row * itemWidth));
column++;
if (column >= columns) {
column = 0;
row++;
}
addOption(race, top);
}
top = height - 20;
if (SELECTION == -1) {
SELECTION = options.size() / 2;
}
scroll(SELECTION, false);
}
private void addOption(Race race, int x, int y) {
addDrawableChild(new TribeButton(x, y, width, race)).onClick(b -> {
private void addOption(Race race, int y) {
var option = new TribeButton(0, y, width, race);
int index = options.size();
options.add(addDrawableChild(option));
option.onClick(b -> {
finished = true;
scroll(index, false);
client.setScreen(new TribeConfirmationScreen(result -> {
finished = false;
@ -91,12 +97,88 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
}).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
public void finish() {
finished = true;
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
public boolean shouldCloseOnEsc() {
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.mson.api.MsonModel;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia;
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 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 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.ZEBRA, Race.EARTH);
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.SEAPONY, Race.UNICORN);
registerRaceMapping(com.minelittlepony.api.pony.meta.Race.SEAPONY, Race.SEAPONY);
}
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<>();
public static <T extends LivingEntity> void register(FeatureFactory<T> factory) {
REGISTRY.add(factory);
public static void register(FeatureFactory<?>...factories) {
for (var factory : factories) {
REGISTRY.add(factory);
}
}
private final Iterable<Feature<T>> features;

View file

@ -32,7 +32,6 @@ public class ModelPartHooks {
final var bestCandidate = new EnqueudHeadRender();
matrices.push();
part.forEachCuboid(matrices, (entry, name, index, cube) -> {
float x = cube.maxX - cube.minX;
float y = cube.maxY - cube.minY;
@ -47,7 +46,6 @@ public class ModelPartHooks {
bestCandidate.maxSideLength = Math.max(Math.max(x, z), y);
}
});
matrices.pop();
if (bestCandidate.transformation != null) {
head.add(bestCandidate);
@ -75,8 +73,6 @@ public class ModelPartHooks {
matrices.translate(x * PIXEL_SCALE, y * PIXEL_SCALE, z * PIXEL_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.DynamicItemRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.item.ClampedModelPredicateProvider;
import net.minecraft.client.item.ModelPredicateProviderRegistry;
import net.minecraft.client.model.*;
import net.minecraft.client.render.RenderLayer;
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.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.ModelIdentifier;
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.util.Identifier;
import net.minecraft.util.math.RotationAxis;
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 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) {
BuiltinItemRendererRegistry.INSTANCE.register(item, INSTANCE);
ModelPredicateProviderRegistry.register(item, THROWING, (stack, world, entity, seed) -> {
return entity != null && entity.isUsingItem() && entity.getActiveItem() == stack ? 1 : 0;
public static void register(Item...items) {
for (Item item : items) {
BuiltinItemRendererRegistry.INSTANCE.register(item, INSTANCE);
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) {
@ -50,6 +57,11 @@ public class PolearmRenderer implements DynamicItemRenderer {
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
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 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();
}
}

View file

@ -1,7 +1,6 @@
package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
import com.minelittlepony.unicopia.entity.Creature;
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.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
public class SmittenEyesRenderer {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/smitten_eyes.png");
@ -42,9 +40,6 @@ public class SmittenEyesRenderer {
ModelPartHooks.stopCollecting().forEach(head -> {
matrices.push();
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);
matrices.scale(scale, scale, scale);
matrices.translate(0, 0.05F, 0);

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -50,7 +51,7 @@ public class WingsFeatureRenderer<E extends LivingEntity> implements AccessoryFe
}
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) {

View file

@ -4,16 +4,21 @@ import java.util.Optional;
import com.minelittlepony.client.util.render.RenderLayerUtil;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Race;
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.Equine;
import com.minelittlepony.unicopia.entity.ItemImpl;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.duck.LavaAffine;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.render.BackgroundRenderer.FogType;
import net.minecraft.client.render.VertexConsumerProvider.Immediate;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.*;
@ -43,6 +48,20 @@ public class WorldRenderDelegate {
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,
double x, double y, double z, float yaw,
float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
@ -80,6 +99,27 @@ public class WorldRenderDelegate {
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) {
matrices.pop();
@ -162,6 +202,13 @@ public class WorldRenderDelegate {
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.POSITIVE_Z.rotationDegrees(roll));
@ -169,6 +216,12 @@ public class WorldRenderDelegate {
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(diveAngle));
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)) {
ModelPartHooks.startCollecting();
}

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.client.render.entity;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.block.FancyBedBlock.SheetPattern;
import com.minelittlepony.unicopia.block.cloud.CloudBedBlock;
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.ModelTransform;
import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory;
@ -30,14 +32,19 @@ import net.minecraft.util.math.RotationAxis;
import net.minecraft.world.World;
public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBedBlock.Tile> {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/cloud_bed/white.png");
private final ModelPart bedHead;
private final ModelPart bedFoot;
private final ModelPart bedHead = getHeadTexturedModelData().createModel();
private final ModelPart bedFoot = getFootTexturedModelData().createModel();
private final ModelPart bedSheetsHead = getSheetsTexturedModelData(0, 3, 13).createModel();
private final ModelPart bedSheetsFoot = getSheetsTexturedModelData(22, 0, 15).createModel();
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() {
@ -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) {
@Nullable
World world = entity.getWorld();
CloudBedBlock.SheetPattern pattern = entity.getPattern();
VertexConsumer buffer = getBuffer(vertices, entity.getBase());
if (world == null) {
renderModel(matrices, vertices, bedHead, Direction.SOUTH, TEXTURE, light, overlay, false);
renderModel(matrices, vertices, bedFoot, Direction.SOUTH, TEXTURE, light, overlay, true);
renderModel(matrices, vertices, bedHead, Direction.SOUTH, buffer, light, overlay, false, false);
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;
}
@ -77,11 +94,36 @@ public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBed
renderModel(matrices, vertices,
state.get(BedBlock.PART) == BedPart.HEAD ? bedHead : bedFoot,
state.get(BedBlock.FACING),
TEXTURE,
buffer,
getModelLight(entity, light),
overlay,
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) {
@ -95,14 +137,18 @@ public class CloudBedBlockEntityRenderer implements BlockEntityRenderer<CloudBed
).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.translate(0, 0.5625f, translate ? -1 : 0);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90));
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.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();
}
}

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 net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.command.argument.RegistryKeyArgumentType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.CommandManager.RegistrationEnvironment;
@ -32,8 +31,6 @@ class SpeciesCommand {
}
}
RegistryKeyArgumentType<Race> raceArgument = Race.argument();
return builder
.then(CommandManager.literal("get")
.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))
))
.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))
.then(CommandManager.argument("target", EntityArgumentType.player())
.executes(context -> set(context.getSource(), EntityArgumentType.getPlayer(context, "target"), Race.fromArgument(context, "race"), false)))
))
.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")))
))
.then(CommandManager.literal("list")
@ -101,7 +98,7 @@ class SpeciesCommand {
boolean first = true;
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(i.getDisplayName());
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.trait.SpellTraits;
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.UItems;
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.stack.Comparison;
import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.recipe.EmiStonecuttingRecipe;
import net.minecraft.item.ItemStack;
import net.minecraft.recipe.RecipeType;
import net.minecraft.registry.DynamicRegistryManager;
@ -30,7 +32,9 @@ import net.minecraft.util.Identifier;
public class Main implements EmiPlugin {
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 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 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 -> {
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.container.SpellbookScreenHandler;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
@ -67,7 +68,7 @@ public interface TrinketsDelegate {
}
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) {

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.ScreenHandlerType;
import net.minecraft.registry.Registry;
import net.minecraft.resource.featuretoggle.FeatureFlags;
import net.minecraft.registry.Registries;
public interface UScreenHandlers {
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) {
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