diff --git a/assets/models/mimic.bbmodel b/assets/models/mimic.bbmodel new file mode 100644 index 00000000..6bff39e0 --- /dev/null +++ b/assets/models/mimic.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"4.9","model_format":"modded_entity","box_uv":true},"name":"mimic","model_identifier":"","modded_entity_version":"Fabric 1.17+","modded_entity_flip_y":true,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"timeline_setups":[],"unhandled_root_fields":{},"resolution":{"width":64,"height":64},"elements":[{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-1,5,5],"to":[1,9,6],"autouv":0,"color":0,"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"9d61ad16-6c82-9a20-5701-12ba5093bf3c"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,5,5],"to":[4,9,6],"autouv":0,"color":0,"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"d25b1a82-7e3e-c44f-623d-6ea50d66066f"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,5,5],"to":[-2,9,6],"autouv":0,"color":0,"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"afb53261-4022-e68b-7664-d6a854d3239f"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,5,-2],"to":[12,9,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"0ffff8f3-55f3-ba96-fd1e-f1364423e084"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,5,-2],"to":[9,9,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"491606c6-699f-841f-a8ab-e8cdec547c68"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,5,-2],"to":[6,9,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"b4218883-bb0c-c89b-1cc8-a056d52c536e"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,5,-13],"to":[6,9,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"85b2bed3-39f9-1e1c-ecd2-d688e58e2901"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,5,-13],"to":[9,9,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"025ec1fd-f7bc-2d27-0393-50f206244268"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,5,-13],"to":[12,9,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"62541bca-746b-c4c1-b4fa-f80ae51de5a7"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-1,4,5],"to":[1,8,6],"autouv":0,"color":0,"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"8c299ddd-aee9-17a1-3a67-2b45fec41dbd"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,4,5],"to":[4,8,6],"autouv":0,"color":0,"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"c3e8c8d9-92d0-b027-aaff-323d36237f18"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,4,5],"to":[-2,8,6],"autouv":0,"color":0,"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"74c2d8e5-4f69-f77d-814a-b71479a7d8a3"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,4,-2],"to":[12,8,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"bea44a0b-36e2-2669-cd89-28e0afd4c448"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,4,-2],"to":[9,8,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"949c17f4-860c-7c24-189a-fa0e74f3666e"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,4,-2],"to":[6,8,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"06536cdb-7e75-261b-c8d5-a32b7a5a0e76"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,4,-13],"to":[12,8,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"0e52be90-4685-d6f1-6b91-c3eb5f8593ca"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,4,-13],"to":[9,8,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"c343e272-4edc-25ec-e874-f562d4f6f0ec"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,4,-13],"to":[6,8,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"1ee91a5f-63e6-af9d-582c-21b579f93773"},{"name":"tongue","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-3,9,1],"to":[3,10,9],"autouv":0,"color":0,"rotation":[-30.000000000000068,0,0],"origin":[0,0,0],"uv_offset":[11,34],"faces":{"north":{"uv":[19,42,25,43],"texture":0},"east":{"uv":[11,42,19,43],"texture":0},"south":{"uv":[33,42,39,43],"texture":0},"west":{"uv":[25,42,33,43],"texture":0},"up":{"uv":[25,42,19,34],"texture":0},"down":{"uv":[31,34,25,42],"texture":0}},"type":"cube","uuid":"5370d06e-ecbb-fb06-26bb-8225234d4589"},{"name":"left_leg","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[1,-7,-2],"to":[6,0,4],"autouv":0,"color":5,"mirror_uv":true,"origin":[3.5,-1.5,1],"uv_offset":[7,30],"faces":{"north":{"uv":[18,36,13,43],"texture":0},"east":{"uv":[24,36,18,43],"texture":0},"south":{"uv":[29,36,24,43],"texture":0},"west":{"uv":[13,36,7,43],"texture":0},"up":{"uv":[13,36,18,30],"texture":0},"down":{"uv":[18,30,23,36],"texture":0}},"type":"cube","uuid":"8c49d0c6-dadb-9406-8104-787cfd570ce0"},{"name":"right_leg","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-7,-2],"to":[-1,0,4],"autouv":0,"color":5,"origin":[-3.5,-1.5,1],"uv_offset":[7,30],"faces":{"north":{"uv":[13,36,18,43],"texture":0},"east":{"uv":[7,36,13,43],"texture":0},"south":{"uv":[24,36,29,43],"texture":0},"west":{"uv":[18,36,24,43],"texture":0},"up":{"uv":[18,36,13,30],"texture":0},"down":{"uv":[23,30,18,36],"texture":0}},"type":"cube","uuid":"5ffba0e0-b2f3-b6a4-f42b-ed2036386dd2"}],"outliner":[{"name":"lid","origin":[0,7,-7],"rotation":[47.5,0,-180],"color":0,"uuid":"704f8283-e7f5-5ff9-eb85-66ed5a7e1199","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["5370d06e-ecbb-fb06-26bb-8225234d4589",{"name":"upper_teeth","origin":[0,0,0],"color":0,"uuid":"6ece85ba-c460-6c7c-1bbd-2f4390cf4c44","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["8c299ddd-aee9-17a1-3a67-2b45fec41dbd","c3e8c8d9-92d0-b027-aaff-323d36237f18","74c2d8e5-4f69-f77d-814a-b71479a7d8a3","bea44a0b-36e2-2669-cd89-28e0afd4c448","949c17f4-860c-7c24-189a-fa0e74f3666e","06536cdb-7e75-261b-c8d5-a32b7a5a0e76","0e52be90-4685-d6f1-6b91-c3eb5f8593ca","c343e272-4edc-25ec-e874-f562d4f6f0ec","1ee91a5f-63e6-af9d-582c-21b579f93773"]}]},{"name":"lower_teeth","origin":[0,8,-7],"color":0,"uuid":"95035b43-0bfe-81c7-51ac-bb824489552b","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["9d61ad16-6c82-9a20-5701-12ba5093bf3c","d25b1a82-7e3e-c44f-623d-6ea50d66066f","afb53261-4022-e68b-7664-d6a854d3239f","0ffff8f3-55f3-ba96-fd1e-f1364423e084","491606c6-699f-841f-a8ab-e8cdec547c68","b4218883-bb0c-c89b-1cc8-a056d52c536e","62541bca-746b-c4c1-b4fa-f80ae51de5a7","025ec1fd-f7bc-2d27-0393-50f206244268","85b2bed3-39f9-1e1c-ecd2-d688e58e2901"]},{"name":"right_leg","origin":[-3.5,-1.5,1],"color":0,"uuid":"b5b5d56e-c3d8-dd8c-2cb3-37962d9547b3","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["5ffba0e0-b2f3-b6a4-f42b-ed2036386dd2"]},{"name":"left_leg","origin":[-3.5,-1.5,1],"color":0,"uuid":"7589ea26-bfc2-f4e1-bb5b-461462d548b6","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["8c49d0c6-dadb-9406-8104-787cfd570ce0"]}],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/normal.png","name":"normal.png","folder":"","namespace":"","id":"0","width":64,"height":64,"uv_width":64,"uv_height":64,"particle":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"b2322db6-1c29-09ad-3b26-7e50165557cd","relative_path":"../../../../normal.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAABqNJREFUeF7tWltsVEUY/s45u91Lu+22lKK0pPHS2qBpA+USQyFEE4IRYnww0tRojJcYbxgfeDPGmPBojNEXjRhBom8EwZCgRgMtKLaUbYQUURCllQItrS273cvZMf9MZ/fslu7ZXTlwTtl5ObOz55sz8883/z/zzSgA0NfXxwYHB9HS0gJ6dnV1KVSeKy1tWsIq/F6oAHSI1zUwnlfAUlAGBXoS0FQgyRgikWkUg+sbOGPaJrM2X+9/Zdu2bby19fX1GBoa4u9QfuvWrTk/uHrZfWzzMi/i+iTH1AUq+PPS5FRGvtrjhbvMhathBfv6p/k7xeB+7j9trQHa2toQCoV4AylvxgIywNNrPNj34wDHTMSAqjLxzE7lGvD4w63Y2RPlfxGuLiBYcmF8HA3BIH9mp4U+PyZiHo6zzADbt2/nLeno6EB3dzdvA+XXrVuX0+Kr2prYluUKvjnyGwyMx8xsyOwLAx5d04yvjotOE+7OQBRx3ZV6z60lZhmA/r8S1jjuWMiiKbB7927eKjn/Zb69vd10CnSu1LD/8Cm4NNH27NGnkZdp09ql2PVTHC5NBeFqK3R4PGUpFhh7TyMv05UpjeMs8wGzCZtfSXtrE9u0TMfBo2c5gOif0AX22syT8n4FcLuB1e13Y3+/sAjhgv44zxP9o1Exby5HwqmP3+FW4SqvxK/D1zjOdgbI9gHxOBBmwhCUpDEoTwzZvL4VOw5FOAOMPiBx7V9cjCe5IShJY1CeGHJpUuE42xmAfMBTK5LYe/iPTMoYJ046GuKxtffgi14KmgDhgt5MmNEHGH3D+DQ4zjIfkB/hZ78lGbDn+4GUD6C3jH5A0p/KiQHGKFBVFk35APrfGAUk/amcGGBpFCjWANIH9BwTPkBSnQxgdH5UTj5hw4OZPqBpgTuFI6qTAYzOT/qE8bDbvj5gY2s8wwlKBhjXA8QCRQUoCnz5i/COhDM6QckA43qAWKC7vKAoQDjL1gHFMmBlWzPrXA6xDpgrXccH0NKYcLQOmCs5xgfQkrZSmUJS8/C+XI2Kpa4x0VKYyt1aAHt6wzwKFIOzXRS4v7mReb0zMY+HOuHhGWPQkyz122iMickwNFVFMbjB3/+2Zi/Q2LCI1dZU8nZqauY3ZEeoU6nEGMor/Dh7/iIKwVFdlYEbiwtHjIxT4PcJJlKS3/uhJ5TTcAqN5AvryzlI1aMYTYjO0u4ue2cnKz96Loihi6O41biT56YQ0IBJHWhaXIWGWh9vs7GdBw8dz20A2te/9YhqujGRlZKD2hkKYHhkFLcaNzwWRu+QiCwr6jU0LkxPSdnO77pP5DZAy71L2LtPBEFLUgo7coNClV5vjU7lB04uxF/Dl1EIjkLcJz3uG477cySCWCyOxjpvxl5Cfs+UAbSgWRAMzPLecV2HS1WhKMKA9Fsmt6ZhdHwSTsCZGmBWz/MsmDeSWJ79nfXavJHE/o8BipG26HuEe+VDIaWZpY9eFZso2y2FpSRWqLRFHSZJ7M3PciyhDVZ571khpdlyO1yMtCUlsTc+PmU2+Pz/918UUprtlsJyO1yotEWdIknsnc/T2+hclnj7GbGNtp0BpCAi5e18pS0piRXiA2wtiRUqbdFokyT2+qdZUtocNPjgOSGl2dIHkDcvVNqaN1FA+oBCpa155QOKkbbIAITL1wlSFLC1JFaotCUlsXzXAbb2AcVIWyVJzG6S2F1LFjGf15O6xJB91KuCITlz5CvyIkWmY3AC7tSZ3Foil8Q6O4Ko9jPEYwmu4M512UGGaimJFYKjOvcOuLiUdjNxpnoAGeDlDVU8nhtPZ2Vn5WGFPLykclJ2qCNOwJkagISN5zt8qPXrmOuSQloJEpcYdhwPYmhkDIXgSKPbFaq46bhvu01UYdIEX3oowC8syJTNhOyj65295VzbcwLOlAFkgCfXuPHA4nIujNJZvUzysFIeXsryrwdq8M/IGJyAMz0XIAO8trGaX1rKvpxAHaZTW+NxtfQBxAAn4EwZQD5gyyofKtKSOlxqejokkoaLPjMUOHDGx+dyITiqc99p4QNuFG7kahiXZi4k1FR6UV+TvnUhv2fqAygKbFpRnaJ9MhbFVHoWoNKbPm5KJJJwuVT0X1B5FLjVuBPnJ1LtbqgNoK7ax38b22nKgNv+bDAvYW4ev2TJkbOT7FUygJNGy4q2lhhghVWdVGeJAU4aLSvaWmKAFVZ1Up0lBjhptKxoa4kBVljVSXWWGOCk0bKirSUGWGFVJ9VZYoCTRsuKtt72DPgPhw24jE6OT1AAAAAASUVORK5CYII="}],"fabricOptions":{"header":"package com.example.mod;","entity":"Entity","render":"","members":""}} \ No newline at end of file diff --git a/assets/models/mimic.java b/assets/models/mimic.java new file mode 100644 index 00000000..6597d348 --- /dev/null +++ b/assets/models/mimic.java @@ -0,0 +1,66 @@ +// Made with Blockbench 4.9.4 +// Exported for Minecraft version 1.17+ for Yarn +// Paste this class into your mod and generate all required imports + +package com.example.mod; + +public class mimic extends EntityModel { + private final ModelPart lid; + private final ModelPart tongue_r1; + private final ModelPart upper_teeth; + private final ModelPart cube_r1; + private final ModelPart lower_teeth; + private final ModelPart cube_r2; + private final ModelPart right_leg; + private final ModelPart left_leg; + public mimic(ModelPart root) { + this.lid = root.getChild("lid"); + this.lower_teeth = root.getChild("lower_teeth"); + this.right_leg = root.getChild("right_leg"); + this.left_leg = root.getChild("left_leg"); + } + public static TexturedModelData getTexturedModelData() { + ModelData modelData = new ModelData(); + ModelPartData modelPartData = modelData.getRoot(); + ModelPartData lid = modelPartData.addChild("lid", ModelPartBuilder.create(), ModelTransform.of(0.0F, 17.0F, -7.0F, -0.829F, 0.0F, -3.1416F)); + + ModelPartData tongue_r1 = lid.addChild("tongue_r1", ModelPartBuilder.create().uv(11, 34).cuboid(-3.0F, -10.0F, 1.0F, 6.0F, 1.0F, 8.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, 7.0F, 7.0F, 0.5236F, 0.0F, 0.0F)); + + ModelPartData upper_teeth = lid.addChild("upper_teeth", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -8.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-4.0F, -8.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(2.0F, -8.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 7.0F, 7.0F)); + + ModelPartData cube_r1 = upper_teeth.addChild("cube_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-6.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-9.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-12.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-6.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-9.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-12.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, -7.0F, -7.0F, 0.0F, 1.5708F, 0.0F)); + + ModelPartData lower_teeth = modelPartData.addChild("lower_teeth", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -1.0F, 12.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-4.0F, -1.0F, 12.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(2.0F, -1.0F, 12.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 16.0F, -7.0F)); + + ModelPartData cube_r2 = lower_teeth.addChild("cube_r2", ModelPartBuilder.create().uv(0, 0).cuboid(-6.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-9.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-12.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-6.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-9.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)) + .uv(0, 0).cuboid(-12.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, 0.0F, 0.0F, 0.0F, 1.5708F, 0.0F)); + + ModelPartData right_leg = modelPartData.addChild("right_leg", ModelPartBuilder.create().uv(7, 30).cuboid(-2.5F, -1.5F, -3.0F, 5.0F, 7.0F, 6.0F, new Dilation(0.0F)), ModelTransform.pivot(3.5F, 25.5F, 1.0F)); + + ModelPartData left_leg = modelPartData.addChild("left_leg", ModelPartBuilder.create().uv(7, 30).mirrored().cuboid(-9.5F, -1.5F, -3.0F, 5.0F, 7.0F, 6.0F, new Dilation(0.0F)).mirrored(false), ModelTransform.pivot(3.5F, 25.5F, 1.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); + lower_teeth.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha); + right_leg.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha); + left_leg.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/UTags.java b/src/main/java/com/minelittlepony/unicopia/UTags.java index 83e9cb37..0dc7cec4 100644 --- a/src/main/java/com/minelittlepony/unicopia/UTags.java +++ b/src/main/java/com/minelittlepony/unicopia/UTags.java @@ -104,6 +104,7 @@ public interface UTags { TagKey KICKS_UP_DUST = block("kicks_up_dust"); TagKey POLEARM_MINEABLE = block("mineable/polearm"); + TagKey MIMIC_CHESTS = block("mimic_chests"); TagKey BUTTERFLIES_SPAWNABLE_ON = block("butterflies_spawn_on"); diff --git a/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java b/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java index abccbe5f..aed78d3b 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java @@ -3,6 +3,8 @@ package com.minelittlepony.unicopia.block; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect; +import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.server.world.WeatherConditions; import com.mojang.serialization.MapCodec; @@ -58,6 +60,9 @@ public class WeatherVaneBlock extends BlockWithEntity { private float clientAngle; private float prevAngle; + private float lastAngle; + + private Vec3d airflow = Vec3d.ZERO; public WeatherVane(BlockPos pos, BlockState state) { super(UBlockEntities.WEATHER_VANE, pos, state); @@ -70,11 +75,14 @@ public class WeatherVaneBlock extends BlockWithEntity { @Override public void readNbt(NbtCompound nbt) { angle = nbt.getFloat("angle"); + airflow = new Vec3d(nbt.getDouble("windX"), 0, nbt.getDouble("windZ")); } @Override protected void writeNbt(NbtCompound nbt) { nbt.putFloat("angle", angle); + nbt.putDouble("windX", airflow.x); + nbt.putDouble("windZ", airflow.z); } @Override @@ -89,21 +97,24 @@ public class WeatherVaneBlock extends BlockWithEntity { public static void serverTick(World world, BlockPos pos, BlockState state, WeatherVane entity) { Vec3d airflow = WeatherConditions.get(world).getWindDirection(); - float angle = (float)Math.atan2(airflow.x, airflow.z) + MathHelper.PI; - if (Math.signum(entity.angle) != Math.signum(angle)) { - angle = MathHelper.PI - angle; - } - angle %= MathHelper.PI; + float angle = (WeatherConditions.get(world).getWindYaw() % MathHelper.PI); + entity.lastAngle = entity.prevAngle; + entity.prevAngle = entity.angle; if (angle != entity.angle) { entity.angle = angle; + + entity.airflow = airflow; entity.markDirty(); - if (world instanceof ServerWorld serverWorld) { - serverWorld.getChunkManager().markForUpdate(pos); + if (world instanceof ServerWorld sw) { + sw.getChunkManager().markForUpdate(pos); } - world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), USounds.BLOCK_WEATHER_VANE_ROTATE, SoundCategory.BLOCKS, 1, 0.5F + (float)world.random.nextGaussian()); + if (entity.lastAngle == entity.prevAngle) { + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), USounds.BLOCK_WEATHER_VANE_ROTATE, SoundCategory.BLOCKS, 1, 0.5F + (float)world.random.nextGaussian()); + } } + } public static void clientTick(World world, BlockPos pos, BlockState state, WeatherVane entity) { @@ -118,6 +129,18 @@ public class WeatherVaneBlock extends BlockWithEntity { } else if (entity.clientAngle > angle) { entity.clientAngle -= step; } + + if (world.random.nextInt(3) == 0) { + float radius = 10; + for (int i = 0; i < 5; i++) { + world.addImportantParticle(new TargetBoundParticleEffect(UParticles.WIND, null), + world.getRandom().nextTriangular(pos.getX(), radius), + world.getRandom().nextTriangular(pos.getY(), radius), + world.getRandom().nextTriangular(pos.getZ(), radius), + entity.airflow.x / 10F, 0, entity.airflow.z / 10F + ); + } + } } } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java b/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java index 186b0a39..1e049fae 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java @@ -104,7 +104,7 @@ public class KeyBindingsHandler { changePage(client, maxPage, -1); } else if (page < maxPage && pageUp.getState() == PressedState.PRESSED) { changePage(client, maxPage, 1); - } else { + } else if (!client.player.isSpectator()) { for (Binding i : keys.keySet()) { AbilitySlot slot = keys.get(i); if (slot == AbilitySlot.PRIMARY && client.options.sneakKey.isPressed() && abilities.isFilled(AbilitySlot.PASSIVE)) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 704f90cb..ffb412b5 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -22,6 +22,8 @@ import com.minelittlepony.unicopia.client.particle.RainbowTrailParticle; import com.minelittlepony.unicopia.client.particle.RaindropsParticle; import com.minelittlepony.unicopia.client.particle.ShockwaveParticle; import com.minelittlepony.unicopia.client.particle.SphereParticle; +import com.minelittlepony.unicopia.client.particle.SpiralParticle; +import com.minelittlepony.unicopia.client.particle.WindParticle; import com.minelittlepony.unicopia.client.render.*; import com.minelittlepony.unicopia.client.render.entity.*; import com.minelittlepony.unicopia.client.render.shader.UShaders; @@ -79,8 +81,10 @@ public interface URenderers { ParticleFactoryRegistry.getInstance().register(UParticles.RAIN_DROPS, createFactory(RaindropsParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.HEALTH_DRAIN, createFactory(HealthDrainParticle::create)); ParticleFactoryRegistry.getInstance().register(UParticles.FOOTPRINT, createFactory(FootprintParticle::new)); + ParticleFactoryRegistry.getInstance().register(UParticles.SPIRAL, createFactory(SpiralParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_RING, RainboomParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_TRAIL, RainbowTrailParticle::new); + ParticleFactoryRegistry.getInstance().register(UParticles.WIND, WindParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.SHOCKWAVE, ShockwaveParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.SPHERE, SphereParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.DISK, DiskParticle::new); @@ -112,6 +116,7 @@ public interface URenderers { EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new); EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new); EntityRendererRegistry.register(UEntities.SPECTER, EmptyEntityRenderer::new); + EntityRendererRegistry.register(UEntities.MIMIC, MimicEntityRenderer::new); BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new); BlockEntityRendererFactories.register(UBlockEntities.FANCY_BED, CloudBedBlockEntityRenderer::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java index 59b89037..f24dd0e5 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.client.gui; import java.util.List; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.*; @@ -24,6 +25,8 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; @@ -303,6 +306,34 @@ public class UHud { context.fill(0, 0, scaledWidth, scaledHeight, 0x00FF0000 | (alpha << 24)); context.fillGradient(0, (int)(scaledHeight / 1.5), scaledWidth, scaledHeight, 0x00FF0000, 0xAAFF0000); } + + if (pony.getPhysics().isFlyingSurvival) { + float effectStrength = (float)MathHelper.clamp(pony.getPhysics().getClientVelocity().length() / 15F, 0, 1); + + VertexConsumer vertexConsumer = context.getVertexConsumers().getBuffer(RenderLayer.getGui()); + + float innerRadiusPulse = MathHelper.cos((pony.asEntity().age + tickDelta) / 2F) * 6 + (effectStrength * scaledHeight / 2F); + + double points = 22; + float wedgeAngle = 0.05F + MathHelper.sin((pony.asEntity().age + tickDelta) / 3F) * 0.01F; + float outerRadius = Math.max(scaledWidth, scaledHeight); + float alpha = effectStrength * (0.6F + Math.abs(MathHelper.sin((pony.asEntity().age + tickDelta) / 10F))); + context.getMatrices().push(); + context.getMatrices().translate(scaledWidth / 2F, scaledHeight / 2F, 0); + Matrix4f matrix4f = context.getMatrices().peek().getPositionMatrix(); + for (int i = 0; i < points; i++) { + float angle = (MathHelper.TAU * i / (float)points) - wedgeAngle * 0.5F; + float innerRadius = Math.max(0, (scaledHeight / 2F) + (i % 2) * 72 + 14 * (1 - effectStrength) - innerRadiusPulse); + float centerX = MathHelper.sin(angle) * innerRadius; + float centerY = MathHelper.cos(angle) * innerRadius; + + vertexConsumer.vertex(matrix4f, centerX, centerY, 0).color(1F, 1F, 1F, alpha * 0.3F).next(); + vertexConsumer.vertex(matrix4f, MathHelper.sin(angle - wedgeAngle) * outerRadius, MathHelper.cos(angle - wedgeAngle) * outerRadius, 0).color(1F, 1F, 1F, alpha).next(); + vertexConsumer.vertex(matrix4f, MathHelper.sin(angle + wedgeAngle) * outerRadius, MathHelper.cos(angle + wedgeAngle) * outerRadius, 0).color(1F, 1F, 1F, alpha).next(); + vertexConsumer.vertex(matrix4f, centerX, centerY, 0).color(1F, 1F, 1F, alpha * 0.3F).next(); + } + context.getMatrices().pop(); + } } private void renderVignette(DrawContext context, int color, float alpha, float radius, int scaledWidth, int scaledHeight) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java index 978fede1..4a8d7b26 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java @@ -29,7 +29,7 @@ public class RainbowTrailParticle extends AbstractBillboardParticle { public RainbowTrailParticle(TargetBoundParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { super(world, x, y, z, velocityX, velocityY, velocityZ); - trail = new Trail(new Vec3d(x, y, z)); + trail = new Trail(new Vec3d(x, y, z), 1); setMaxAge(300); this.velocityX = velocityX; this.velocityY = velocityY; diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/SpiralParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/SpiralParticle.java new file mode 100644 index 00000000..58559498 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/SpiralParticle.java @@ -0,0 +1,80 @@ +package com.minelittlepony.unicopia.client.particle; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.particle.SpiralParticleEffect; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.particle.NoRenderParticle; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.SpriteProvider; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class SpiralParticle extends NoRenderParticle { + + private final SpiralParticleEffect parameters; + + @Nullable + private final Particle particle; + + private float scale; + + public SpiralParticle(SpiralParticleEffect parameters, SpriteProvider provider, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { + super(world, x, y, z, velocityX, velocityY, velocityZ); + this.scale = 0.1f * (random.nextFloat() * 0.5f + 0.5f) * 2.0f; + setMaxAge(3); + scale(0.125F); + this.parameters = parameters; + this.collidesWithWorld = false; + this.particle = MinecraftClient.getInstance().particleManager.addParticle(parameters.effect(), x, y, z, velocityX, velocityY, velocityZ); + this.particle.setMaxAge(1000); + } + + @Override + public Particle scale(float scale) { + this.scale *= scale; + super.scale(scale); + return this; + } + + @Override + public void move(double dx, double dy, double dz) { + super.move(dx, dy, dz); + if (particle != null) { + particle.setPos(x, y, z); + } + } + + @Override + public void tick() { + if (particle == null || !particle.isAlive()) { + markDead(); + } + + super.tick(); + + Vec3d target = parameters.centerPoint().getPosition(world); + Vec3d pos = new Vec3d(x, y, z); + + if (scale * 1.5F < 0.5F) { + scale(1.5F); + } + + double distance = pos.distanceTo(target); + if (distance > 0) { + age = 0; + } + + Vec3d radial = target.subtract(pos).normalize(); + Vec3d tangent = radial.rotateY(MathHelper.HALF_PI).multiply(parameters.angularVelocity() * 0.9F); + Vec3d motion = radial.multiply(parameters.angularVelocity() * 0.1F).add(tangent); + move(motion.x, motion.y, motion.z); + } + + @Override + public String toString() { + return super.toString() + ", Angular Velocity " + parameters.angularVelocity() + ", Target (" + parameters.centerPoint() + ") Sub-Particle (" + particle + ")"; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java new file mode 100644 index 00000000..245c2b06 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java @@ -0,0 +1,104 @@ +package com.minelittlepony.unicopia.client.particle; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.client.render.bezier.BezierSegment; +import com.minelittlepony.unicopia.client.render.bezier.Trail; +import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.BufferBuilder; +import net.minecraft.client.render.Tessellator; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class WindParticle extends AbstractBillboardParticle { + private static final Identifier TEXTURE = Unicopia.id("textures/particle/wind.png"); + + private final Trail trail; + + @Nullable + private Entity target; + + private int attachmentTicks; + + private final Vec3d offset; + private final boolean passive; + + public WindParticle(TargetBoundParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { + super(world, x, y, z, velocityX, velocityY, velocityZ); + trail = new Trail(new Vec3d(x, y, z), 0.02F); + setMaxAge(300); + this.alpha = 0.15F; + this.velocityX = velocityX; + this.velocityY = velocityY; + this.velocityZ = velocityZ; + this.attachmentTicks = (int)world.random.nextTriangular(15, 12); + this.passive = effect.getTargetId() <= 0; + this.collidesWithWorld = false; + + if (effect.getTargetId() > 0) { + this.target = world.getEntityById(effect.getTargetId()); + } + offset = target == null ? Vec3d.ZERO : new Vec3d(x, y, z).subtract(target.getPos()); + } + + @Override + protected Identifier getTexture() { + return TEXTURE; + } + + @Override + public boolean isAlive() { + return age < getMaxAge() && (!dead || !trail.getSegments().isEmpty()); + } + + @Override + protected void renderQuads(Tessellator te, BufferBuilder buffer, float x, float y, float z, float tickDelta) { + float alpha = this.alpha * (1 - (float)age / maxAge); + + List segments = trail.getSegments(); + + for (int i = 0; i < segments.size() - 1; i++) { + BezierSegment corners = segments.get(i).getPlane(segments.get(i + 1)); + float scale = getScale(tickDelta); + + corners.forEachCorner(corner -> { + corner.position().mul(scale).add(x, y, z); + }); + + renderQuad(te, buffer, corners.corners(), segments.get(i).getAlpha() * alpha, tickDelta); + } + } + + @Override + public void tick() { + super.tick(); + + float animationFrame = age + MinecraftClient.getInstance().getTickDelta(); + + float sin = MathHelper.sin(animationFrame / 5F) * 0.1F; + float cos = MathHelper.cos(animationFrame / 10F) * 0.2F; + + if (passive) { + trail.update(new Vec3d(x + cos, y + sin, z - cos)); + } else { + if (target != null && target.isAlive()) { + trail.update(target.getPos().add(offset).add(cos, sin, -cos)); + + if (attachmentTicks > 0 && --attachmentTicks <= 0) { + target = null; + } + } + } + + if (trail.tick()) { + markDead(); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java index 4d8ecef3..40a83c9d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java @@ -7,7 +7,8 @@ import com.minelittlepony.unicopia.compat.pehkui.PehkUtil; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.behaviour.Disguise; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; -import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour; +import com.minelittlepony.unicopia.mixin.MixinBlockEntity; + import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.MinecraftClient; @@ -86,7 +87,7 @@ class EntityDisguiseRenderer { if (blockEntity != null) { BlockEntityRenderer r = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(blockEntity); if (r != null) { - ((FallingBlockBehaviour.Positioned)blockEntity).setPos(e.getBlockPos()); + ((MixinBlockEntity)blockEntity).setPos(e.getBlockPos()); blockEntity.setWorld(e.getWorld()); matrices.push(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java b/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java index f130fbcc..8bfd4c5d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java @@ -12,8 +12,11 @@ public class Trail { public final Vec3d pos; - public Trail(Vec3d pos) { + private final float height; + + public Trail(Vec3d pos, float height) { this.pos = pos; + this.height = height; segments.add(new Segment(pos)); } @@ -61,7 +64,7 @@ public class Trail { } public BezierSegment getPlane(Segment to) { - return new BezierSegment(offset, to.offset, 1); + return new BezierSegment(offset, to.offset, height); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java index 20662111..d1dfa12a 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java @@ -59,7 +59,7 @@ public class CloudChestBlockEntityRenderer extends ChestBlockEntityRenderer getProperties(BlockState state, ChestBlockEntity entity) { + public static DoubleBlockProperties.PropertySource getProperties(BlockState state, ChestBlockEntity entity) { return entity.getWorld() != null ? ((AbstractChestBlock)state.getBlock()).getBlockEntitySource(state, entity.getWorld(), entity.getPos(), true) : DoubleBlockProperties.PropertyRetriever::getFallback; diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java index 5404bb6e..94bae2a7 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java @@ -9,10 +9,8 @@ import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.EntityRendererFactory; -import net.minecraft.client.render.model.ModelLoader; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.Identifier; -import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; public class CrystalShardsEntityRenderer extends EntityRenderer { @@ -30,6 +28,8 @@ public class CrystalShardsEntityRenderer extends EntityRenderer layer.hasCrumbling() ? VertexConsumers.union(destructionOverlay, vertices.getBuffer(layer)) : vertices.getBuffer(layer); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java new file mode 100644 index 00000000..1195e6c1 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java @@ -0,0 +1,166 @@ +package com.minelittlepony.unicopia.client.render.entity; + +import com.minelittlepony.unicopia.entity.mob.MimicEntity; + +import net.minecraft.block.ChestBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.client.MinecraftClient; +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.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.*; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationAxis; + +public class MimicEntityRenderer extends MobEntityRenderer { + private static final Identifier TEXTURE = new Identifier("textures/entity/chest/normal.png"); + + public MimicEntityRenderer(EntityRendererFactory.Context context) { + super(context, new MimicModel(MimicModel.getTexturedModelData().createModel()), 0); + addFeature(new ChestFeature(this)); + } + + @Override + public void render(MimicEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) { + matrices.push(); + matrices.translate(0, 0.3F * entity.getPeekAmount(), 0); + + float legAngle = entity.limbAnimator.getPos(tickDelta); + float legSpeed = entity.limbAnimator.getSpeed(tickDelta); + + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(MathHelper.cos(legAngle * 0.6662F) * 1.4F * legSpeed * 10 * entity.getPeekAmount())); + super.render(entity, yaw, tickDelta, matrices, + FloatingArtefactEntityRenderer.getDestructionOverlayProvider( + matrices, + vertices, + 1, + FloatingArtefactEntityRenderer.getDestructionStage(entity) + ), light); + matrices.pop(); + } + + @Override + public Identifier getTexture(MimicEntity entity) { + return TEXTURE; + } + + @Override + protected float getLyingAngle(MimicEntity entity) { + return 0; + } + + static class ChestFeature extends FeatureRenderer { + public ChestFeature(FeatureRendererContext context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, MimicEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + BlockEntity tileData = entity.getChestData(); + if (tileData != null) { + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-entity.getPitch(tickDelta))); + matrices.push(); + matrices.translate(-0.5, -1.5, -0.5); + MinecraftClient.getInstance().getBlockEntityRenderDispatcher().render(tileData, tickDelta, matrices, vertexConsumers); + matrices.pop(); + } + } + + } + + static class MimicModel extends EntityModel { + private ModelPart part; + private ModelPart lid; + private ModelPart leftLeg; + private ModelPart rightLeg; + + public MimicModel(ModelPart part) { + this.part = part; + this.lid = part.getChild("lid"); + this.leftLeg = part.getChild("left_leg"); + this.rightLeg = part.getChild("right_leg"); + } + + public static TexturedModelData getTexturedModelData() { + ModelData data = new ModelData(); + ModelPartData root = data.getRoot(); + ModelPartData lid = root.addChild("lid", ModelPartBuilder.create(), ModelTransform.of(0, 15, -7, 0, 0, -3.1416F)); + lid.addChild("tongue", ModelPartBuilder.create() + .uv(11, 34).cuboid(-3, -11, 1, 6, 1, 8, Dilation.NONE), ModelTransform.of(0, 6, 9, 0.8F, 0, 0)); + lid.addChild("upper_teeth", ModelPartBuilder.create() + //.uv(0, 0).cuboid(-7, 0, 0, 14, 5, 14, Dilation.NONE) + .uv(0, 0).cuboid(-1, -2, 12, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-4, -2, 12, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(2, -2, 12, 2, 4, 1, Dilation.NONE), ModelTransform.NONE) + .addChild("cube_r1", ModelPartBuilder.create() + .uv(0, 0).cuboid(-5, -2, -6, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-8, -2, -6, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-11, -2, -6, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-5, -2, 5, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-8, -2, 5, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-11, -2, 5, 2, 4, 1, Dilation.NONE), ModelTransform.of(0, 0, 0, 0, 1.5708F, 0)); + root.addChild("lower_teeth", ModelPartBuilder.create() + .uv(0, 0).cuboid(-1, -1, 12, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-4, -1, 12, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(2, -1, 12, 2, 4, 1, Dilation.NONE), ModelTransform.pivot(0, 13, -7)) + .addChild("cube_r2", ModelPartBuilder.create() + .uv(0, 0).cuboid(-6, -1, -6, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-9, -1, -6, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-12, -1, -6, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-6, -1, 5, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-9, -1, 5, 2, 4, 1, Dilation.NONE) + .uv(0, 0).cuboid(-12, -1, 5, 2, 4, 1, Dilation.NONE), ModelTransform.of(0, 0, 0, 0, 1.5708F, 0)); + root.addChild("right_leg", ModelPartBuilder.create() + .uv(7, 30).cuboid(-2.5F, -1.5F, -3.5F, 5, 7, 6, Dilation.NONE), ModelTransform.pivot(3.5F, 23.5F, 0)); + root.addChild("left_leg", ModelPartBuilder.create() + .uv(7, 30).mirrored().cuboid(-9.5F, -1.5F, -3.5F, 5, 7, 6, Dilation.NONE), ModelTransform.pivot(3.5F, 23.5F, 0)); + return TexturedModelData.of(data, 64, 64); + } + + @Override + public void animateModel(MimicEntity entity, float limbAngle, float limbDistance, float tickDelta) { + ChestBlockEntity tileData = entity.getChestData(); + if (tileData != null) { + var properties = CloudChestBlockEntityRenderer.getProperties(tileData.getCachedState(), tileData); + float progress = 1 - (float)Math.pow(1 - properties.apply(ChestBlock.getAnimationProgressRetriever(tileData)).get(tickDelta), 3); + lid.pitch = -(progress * 1.5707964f); + } else { + lid.pitch = 0; + } + part.yaw = MathHelper.RADIANS_PER_DEGREE * 180; + part.pitch = -entity.getPitch(tickDelta) * MathHelper.RADIANS_PER_DEGREE; + } + + @Override + public void setAngles(MimicEntity entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) { + rightLeg.resetTransform(); + leftLeg.resetTransform(); + rightLeg.pitch = MathHelper.cos(limbAngle * 0.6662F) * 1.4F * limbDistance; + leftLeg.pitch = MathHelper.cos(limbAngle * 0.6662F + (float) Math.PI) * 1.4F * limbDistance; + float revealPercentage = entity.getPeekAmount(); + float velocy = (1 - revealPercentage) * -10F; + rightLeg.pivotY += velocy; + leftLeg.pivotY += velocy; + rightLeg.visible = revealPercentage > 0.2F; + leftLeg.visible = revealPercentage > 0.2F; + } + + @Override + public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay, float red, float green, float blue, float alpha) { + part.render(matrices, vertices, light, overlay); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java index 12ddedd2..a8ce0bbb 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java @@ -81,6 +81,10 @@ public class UBlockTagProvider extends FabricTagProvider.BlockTagProvider { .forceAddTag(UTags.Blocks.JARS) .add(Blocks.VINE, Blocks.LILY_PAD); + getOrCreateTagBuilder(UTags.Blocks.MIMIC_CHESTS).add( + Blocks.CHEST, Blocks.TRAPPED_CHEST, UBlocks.CLOUD_CHEST + ); + getOrCreateTagBuilder(UTags.Blocks.INTERESTING).add( Blocks.SEA_LANTERN, Blocks.ENDER_CHEST, Blocks.END_PORTAL_FRAME, Blocks.JUKEBOX, Blocks.SPAWNER diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java index b9d440a4..d932e0cc 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java @@ -6,6 +6,7 @@ import java.util.Optional; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.mixin.MixinBlockEntity; import com.minelittlepony.unicopia.mixin.MixinFallingBlock; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; import com.minelittlepony.unicopia.util.Tickable; @@ -25,7 +26,6 @@ import net.minecraft.entity.FallingBlockEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.state.property.Properties; import net.minecraft.registry.tag.BlockTags; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; @@ -118,13 +118,9 @@ public class FallingBlockBehaviour extends EntityBehaviour { } be.setWorld(entity.getWorld()); - ((Positioned)be).setPos(entity.getBlockPos()); + ((MixinBlockEntity)be).setPos(entity.getBlockPos()); ceb.tick(); be.setWorld(null); } } - - public interface Positioned { - void setPos(BlockPos pos); - } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java new file mode 100644 index 00000000..3cae636c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java @@ -0,0 +1,403 @@ +package com.minelittlepony.unicopia.entity.mob; + +import java.util.HashSet; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.UTags; +import com.minelittlepony.unicopia.item.enchantment.EnchantmentUtil; +import com.minelittlepony.unicopia.mixin.MixinBlockEntity; +import com.minelittlepony.unicopia.util.InventoryUtil; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.block.enums.ChestType; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.MeleeAttackGoal; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.mob.PathAwareEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.loot.LootTables; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtOps; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.NamedScreenHandlerFactory; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.ItemScatterer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +public class MimicEntity extends PathAwareEntity { + static final byte OPEN_MOUTH = (byte)200; + static final byte CLOSE_MOUTH = (byte)201; + static final TrackedData CHEST_DATA = DataTracker.registerData(MimicEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); + static final TrackedData MOUTH_OPEN = DataTracker.registerData(MimicEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + + @Nullable + private ChestBlockEntity chestData; + + private int openTicks; + private final Set observingPlayers = new HashSet<>(); + + public static boolean shouldConvert(World world, BlockPos pos, PlayerEntity player, Identifier lootTable) { + if (!shouldGenerateMimic(lootTable) + || !world.getBlockState(pos).isIn(UTags.Blocks.MIMIC_CHESTS) + || !(world.getBlockEntity(pos) instanceof ChestBlockEntity be) + || be.getCachedState().getOrEmpty(ChestBlock.CHEST_TYPE).orElse(ChestType.SINGLE) != ChestType.SINGLE) { + return false; + } + + int difficulty = world.getDifficulty().ordinal() - 1; + float threshold = 0.35F * ((EnchantmentUtil.getLuck(0, player) / 20F) + 0.5F); + return difficulty > 0 && world.random.nextFloat() < (difficulty / 3F) * threshold; + } + + @SuppressWarnings("deprecation") + @Nullable + public static MimicEntity spawnFromChest(World world, BlockPos pos) { + if (!(world.getBlockEntity(pos) instanceof ChestBlockEntity be)) { + return null; + } + world.removeBlockEntity(pos); + world.setBlockState(pos, Blocks.AIR.getDefaultState()); + MimicEntity mimic = UEntities.MIMIC.create(world); + BlockState state = be.getCachedState(); + Direction facing = state.getOrEmpty(ChestBlock.FACING).orElse(null); + float yaw = facing.asRotation(); + be.setCachedState(be.getCachedState().getBlock().getDefaultState()); + mimic.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, yaw, 0); + mimic.setHeadYaw(yaw); + mimic.setBodyYaw(yaw); + mimic.setYaw(yaw); + mimic.setChest(be); + world.spawnEntity(mimic); + return mimic; + } + + public static boolean shouldGenerateMimic(@Nullable Identifier lootTable) { + return lootTable != null + && lootTable.getPath().indexOf("village") == -1 + && lootTable.getPath().indexOf("bastion") == -1 + && lootTable.getPath().indexOf("underwater") == -1 + && lootTable.getPath().indexOf("shipwreck") == -1; + } + + MimicEntity(EntityType type, World world) { + super(type, world); + ignoreCameraFrustum = true; + } + + @Override + protected void initDataTracker() { + super.initDataTracker(); + dataTracker.startTracking(CHEST_DATA, new NbtCompound()); + dataTracker.startTracking(MOUTH_OPEN, false); + } + + @Override + public boolean isCollidable() { + return isAlive(); + } + + @Override + public boolean canBreatheInWater() { + return true; + } + + @Nullable + @Override + public ItemStack getPickBlockStack() { + Item item = chestData.getCachedState().getBlock().asItem(); + return item == Items.AIR ? null : item.getDefaultStack(); + } + + @Override + protected void initGoals() { + goalSelector.add(2, new AttackGoal(this, 0.6F, false)); + } + + public void setChest(ChestBlock chest) { + if (chest.createBlockEntity(getBlockPos(), chest.getDefaultState()) instanceof ChestBlockEntity be) { + setChest(be); + be.setLootTable(LootTables.ABANDONED_MINESHAFT_CHEST, getWorld().getRandom().nextLong()); + if (!getWorld().isClient) { + dataTracker.set(CHEST_DATA, writeChestData(chestData)); + } + } + } + + public void setChest(ChestBlockEntity chestData) { + this.chestData = chestData; + ((MimicGeneratable)chestData).setAllowMimics(false); + chestData.setWorld(getWorld()); + if (!getWorld().isClient) { + dataTracker.set(CHEST_DATA, writeChestData(chestData)); + } + } + + @Nullable + public ChestBlockEntity getChestData() { + return chestData; + } + + public boolean isMouthOpen() { + return dataTracker.get(MOUTH_OPEN); + } + + public void setMouthOpen(boolean mouthOpen) { + if (mouthOpen == isMouthOpen()) { + return; + } + + playSound(mouthOpen ? SoundEvents.BLOCK_CHEST_OPEN : SoundEvents.BLOCK_CHEST_CLOSE, 0.5F, 1F); + dataTracker.set(MOUTH_OPEN, mouthOpen); + if (chestData != null) { + chestData.onSyncedBlockEvent(1, mouthOpen ? 1 : 0); + } + } + + public void playChompAnimation() { + openTicks = 5; + setMouthOpen(true); + } + + public float getPeekAmount() { + return MathHelper.clamp((float)getVelocity().lengthSquared() * 50F, 0, 1); + } + + @Override + public void tick() { + super.tick(); + + if (!getWorld().isClient) { + if (age < 12 || (getTarget() == null && this.lastAttackedTicks < age - 30)) { + BlockPos pos = getBlockPos(); + setPosition( + pos.getX() + 0.5, + getY(), + pos.getZ() + 0.5 + ); + setBodyYaw(MathHelper.floor(getBodyYaw() / 90) * 90); + setYaw(MathHelper.floor(getYaw() / 90) * 90); + setHeadYaw(MathHelper.floor(getHeadYaw() / 90) * 90); + if (getHealth() < getMaxHealth() && getWorld().random.nextInt(20) == 0) { + heal(1); + } else if (age % 150 == 0 && chestData != null && !isMouthOpen()) { + if (getWorld().getClosestPlayer(this, 15) == null) { + getWorld().setBlockState(getBlockPos(), chestData.getCachedState().withIfExists(ChestBlock.FACING, getHorizontalFacing())); + if (getWorld().getBlockEntity(getBlockPos()) instanceof ChestBlockEntity be) { + InventoryUtil.copyInto(chestData, be); + ((MimicGeneratable)be).setMimic(true); + discard(); + } + } + } + } + + if (!observingPlayers.isEmpty()) { + setMouthOpen(true); + } + } + + if (chestData == null) { + setChest((ChestBlock)Blocks.CHEST); + } + + if (getWorld().isClient) { + ((MixinBlockEntity)chestData).setPos(getBlockPos()); + ChestBlockEntity.clientTick(getWorld(), getBlockPos(), chestData.getCachedState(), chestData); + } + + if (openTicks > 0 && --openTicks <= 0) { + setMouthOpen(false); + } + + if (getTarget() != null) { + if (openTicks <= 0 && age % 20 == 0) { + playChompAnimation(); + } + } + } + + @Override + protected ActionResult interactMob(PlayerEntity player, Hand hand) { + if (getTarget() == null && chestData != null) { + player.openHandledScreen(new NamedScreenHandlerFactory() { + @Override + public Text getDisplayName() { + return chestData.getDisplayName(); + } + + @Override + public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) { + return createScreenHandler(syncId, inv, player); + } + }); + + return ActionResult.SUCCESS; + } + return ActionResult.PASS; + } + + public ScreenHandler createScreenHandler(int syncId, PlayerInventory inv, PlayerEntity player) { + chestData.checkLootInteraction(player); + setChest(chestData); + var inventory = InventoryUtil.copyInto(chestData, new SimpleInventory(chestData.size()) { + @Override + public void onOpen(PlayerEntity player) { + observingPlayers.add(player); + //setMouthOpen(true); + } + + @Override + public void onClose(PlayerEntity player) { + observingPlayers.remove(player); + setMouthOpen(!observingPlayers.isEmpty()); + } + }); + inventory.addListener(sender -> { + if (InventoryUtil.contentEquals(inventory, chestData)) { + return; + } + observingPlayers.clear(); + playChompAnimation(); + setTarget(player); + if (player instanceof ServerPlayerEntity spe) { + spe.closeHandledScreen(); + } + }); + return GenericContainerScreenHandler.createGeneric9x3(syncId, inv, inventory); + } + + @Override + public void setAttacking(boolean attacking) { + super.setAttacking(attacking); + if (attacking) { + playChompAnimation(); + } + } + + @Override + protected void playHurtSound(DamageSource source) { + playChompAnimation(); + if (source.getAttacker() instanceof LivingEntity l) { + setTarget(l); + } + } + + @Override + public void onTrackedDataSet(TrackedData data) { + super.onTrackedDataSet(data); + if (CHEST_DATA.equals(data) && getWorld().isClient) { + setChest(readChestData(dataTracker.get(CHEST_DATA))); + } else if (MOUTH_OPEN.equals(data)) { + if (chestData != null) { + chestData.onSyncedBlockEvent(1, isMouthOpen() ? 1 : 0); + } + } + } + + @Override + public void readCustomDataFromNbt(NbtCompound nbt) { + super.readCustomDataFromNbt(nbt); + if (nbt.contains("chest", NbtElement.COMPOUND_TYPE)) { + chestData = readChestData(nbt.getCompound("chest")); + } else { + chestData = null; + } + } + + @Nullable + private ChestBlockEntity readChestData(NbtCompound nbt) { + BlockState state = BlockState.CODEC.decode(NbtOps.INSTANCE, nbt.getCompound("state")).result().get().getFirst(); + if (BlockEntity.createFromNbt(getBlockPos(), state, nbt.getCompound("data")) instanceof ChestBlockEntity data) { + data.setWorld(getWorld()); + ((MimicGeneratable)data).setAllowMimics(false); + return data; + } + return null; + } + + @Override + public void writeCustomDataToNbt(NbtCompound nbt) { + super.writeCustomDataToNbt(nbt); + if (chestData != null) { + nbt.put("chest", writeChestData(chestData)); + } + } + + @Nullable + private NbtCompound writeChestData(ChestBlockEntity chestData) { + NbtCompound chest = new NbtCompound(); + chest.put("data", chestData.createNbtWithId()); + chest.put("state", BlockState.CODEC.encode(chestData.getCachedState(), NbtOps.INSTANCE, new NbtCompound()).result().get()); + return chest; + } + + @Override + protected void dropLoot(DamageSource damageSource, boolean causedByPlayer) { + if (chestData != null) { + ItemScatterer.spawn(getWorld(), this, chestData); + ItemScatterer.spawn(getWorld(), getX(), getY(), getZ(), chestData.getCachedState().getBlock().asItem().getDefaultStack()); + } + } + + public class AttackGoal extends MeleeAttackGoal { + private int ticks; + + public AttackGoal(PathAwareEntity mob, double speed, boolean pauseWhenMobIdle) { + super(mob, speed, pauseWhenMobIdle); + } + + @Override + public void start() { + super.start(); + this.ticks = 0; + } + + @Override + public void stop() { + super.stop(); + setAttacking(false); + } + + @Override + public void tick() { + super.tick(); + ++ticks; + if (ticks >= 5 && getCooldown() < getMaxCooldown() / 2) { + setAttacking(true); + } else { + setAttacking(false); + } + } + } + + public interface MimicGeneratable { + void setAllowMimics(boolean allowMimics); + + void setMimic(boolean mimic); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java index 95871bb9..dbfdd790 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java @@ -89,6 +89,10 @@ public interface UEntities { .fireImmune() .spawnableFarFromPlayer() .dimensions(EntityDimensions.fixed(1, 2))); + EntityType MIMIC = register("mimic", FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, MimicEntity::new) + .fireImmune() + //.disableSummon() + .dimensions(EntityDimensions.changing(0.875F, 0.875F))); static EntityType register(String name, FabricEntityTypeBuilder builder) { EntityType type = builder.build(); @@ -105,6 +109,7 @@ public interface UEntities { FabricDefaultAttributeRegistry.register(LOOT_BUG, LootBugEntity.createSilverfishAttributes()); FabricDefaultAttributeRegistry.register(IGNOMINIOUS_BULB, IgnominiousBulbEntity.createMobAttributes()); FabricDefaultAttributeRegistry.register(SPECTER, SpecterEntity.createAttributes()); + FabricDefaultAttributeRegistry.register(MIMIC, MimicEntity.createMobAttributes()); if (!Unicopia.getConfig().disableButterflySpawning.get()) { final Predicate butterflySpawnable = BiomeSelectors.foundInOverworld() diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index 19d37345..de13f11d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -649,6 +649,14 @@ public class PlayerPhysics extends EntityPhysics implements Tickab velocity.x += - forward * MathHelper.sin(entity.getYaw() * 0.017453292F); velocity.z += forward * MathHelper.cos(entity.getYaw() * 0.017453292F); + if (pony.isClient()) { + float effectChance = 1F - (float)(MathHelper.clamp(velocity.horizontalLengthSquared(), 0, 1)); + + if (entity.getWorld().random.nextInt(1 + (int)(120 * effectChance)) == 0) { + pony.spawnParticles(new TargetBoundParticleEffect(UParticles.WIND, pony.asEntity()), 3); + } + } + if (entity.getWorld().hasRain(entity.getBlockPos())) { applyTurbulance(velocity); } else { @@ -696,7 +704,10 @@ public class PlayerPhysics extends EntityPhysics implements Tickab if (manualFlap) { descentRate -= 0.5; } else { - descentRate = Math.max(0, descentRate / 2); + descentRate *= 0.25F; + if (velocity.y < 0) { + velocity.y *= 0.6F; + } } } } @@ -710,9 +721,11 @@ public class PlayerPhysics extends EntityPhysics implements Tickab Vec3d direction = entity.getRotationVec(1).normalize().multiply(thrustStrength); - if (hovering) { + if (hovering || !manualFlap) { if (entity.isSneaking()) { - velocity.y -= 0.2F; + velocity.y -= 0.006F; + } else { + velocity.y *= 1 - thrustScale; } } else { velocity.x += direction.x * 1.3F; @@ -815,6 +828,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab pony.updateVelocity(); + pony.spawnParticles(new TargetBoundParticleEffect(UParticles.WIND, pony.asEntity()), 4); + if (isFlying()) { playSound(USounds.ENTITY_PLAYER_PEGASUS_DASH, 1, 1); } else { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java index 8d98283e..2a874a22 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java @@ -1,23 +1,15 @@ package com.minelittlepony.unicopia.mixin; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; - -import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour; +import org.spongepowered.asm.mixin.gen.Accessor; import net.minecraft.block.entity.BlockEntity; import net.minecraft.util.math.BlockPos; @Mixin(BlockEntity.class) -abstract class MixinBlockEntity implements FallingBlockBehaviour.Positioned { - @Shadow +public interface MixinBlockEntity { @Mutable - private @Final BlockPos pos; - - @Override - public void setPos(BlockPos pos) { - this.pos = pos; - } + @Accessor("pos") + void setPos(BlockPos pos); } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java new file mode 100644 index 00000000..dcf21efe --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java @@ -0,0 +1,79 @@ +package com.minelittlepony.unicopia.mixin; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import com.minelittlepony.unicopia.entity.mob.MimicEntity; + +import net.minecraft.block.entity.LockableContainerBlockEntity; +import net.minecraft.block.entity.LootableContainerBlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.util.Identifier; + +@Mixin(LootableContainerBlockEntity.class) +abstract class MixinLootableContainerBlockEntity extends LockableContainerBlockEntity implements MimicEntity.MimicGeneratable { + private Identifier mimicLootTable; + private boolean allowMimics = true; + private boolean isMimic; + + @Shadow + @Nullable + private Identifier lootTableId; + + MixinLootableContainerBlockEntity() { super(null, null, null); } + + @Inject(method = "deserializeLootTable", at = @At("HEAD")) + private void deserializeMimic(NbtCompound nbt, CallbackInfoReturnable info) { + isMimic = nbt.getBoolean("mimic"); + } + + @Inject(method = "serializeLootTable", at = @At("HEAD")) + private void serializeMimic(NbtCompound nbt, CallbackInfoReturnable info) { + nbt.putBoolean("mimic", isMimic); + } + + @Override + public void setAllowMimics(boolean allowMimics) { + this.allowMimics = allowMimics; + this.isMimic &= allowMimics; + markDirty(); + } + + @Override + public void setMimic(boolean mimic) { + isMimic = mimic; + markDirty(); + } + + @Inject(method = "checkLootInteraction", at = @At("HEAD")) + private void onCheckLootInteraction(@Nullable PlayerEntity player, CallbackInfo info) { + if (player != null && allowMimics && lootTableId != null) { + mimicLootTable = lootTableId; + } + } + + @Inject( + method = "createMenu", + at = @At( + value = "INVOKE", + target = "net/minecraft/block/entity/LootableContainerBlockEntity.checkLootInteraction(Lnet/minecraft/entity/player/PlayerEntity;)V", + shift = Shift.AFTER + ), cancellable = true) + private void onCreateMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player, CallbackInfoReturnable info) { + if (player != null && (isMimic || (allowMimics && MimicEntity.shouldConvert(player.getWorld(), getPos(), player, mimicLootTable)))) { + var mimic = MimicEntity.spawnFromChest(player.getWorld(), getPos()); + if (mimic != null) { + info.setReturnValue(mimic.createScreenHandler(syncId, playerInventory, player)); + } + mimicLootTable = null; + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/particle/FollowingParticleEffect.java b/src/main/java/com/minelittlepony/unicopia/particle/FollowingParticleEffect.java index 42482d6a..d59e42f0 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/FollowingParticleEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/FollowingParticleEffect.java @@ -26,10 +26,7 @@ public record FollowingParticleEffect ( this(type, new WeakTarget(reader), ParticleFactoryHelper.readFloat(reader), - ParticleFactoryHelper.readOptional(reader, r -> { - r.expect(' '); - return ParticleFactoryHelper.read(r); - })); + ParticleFactoryHelper.readOptional(reader, r -> ParticleFactoryHelper.read(r))); } protected FollowingParticleEffect(ParticleType type, PacketByteBuf buf) { diff --git a/src/main/java/com/minelittlepony/unicopia/particle/ParticleFactoryHelper.java b/src/main/java/com/minelittlepony/unicopia/particle/ParticleFactoryHelper.java index af291232..be78f0b7 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/ParticleFactoryHelper.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/ParticleFactoryHelper.java @@ -14,17 +14,11 @@ import net.minecraft.registry.Registries; import net.minecraft.util.math.Vec3d; public interface ParticleFactoryHelper { - @SuppressWarnings("deprecation") - PacketCodec PARTICLE_EFFECT_CODEC = new PacketCodec<>( - buf -> { - @SuppressWarnings("unchecked") - ParticleType type = (ParticleType)Registries.PARTICLE_TYPE.get(buf.readInt()); - return type.getParametersFactory().read(type, buf); - }, - (buf, effect) -> { - buf.writeInt(Registries.PARTICLE_TYPE.getRawId(effect.getType())); - effect.write(buf); - } + @SuppressWarnings({ "deprecation", "unchecked", "rawtypes" }) + PacketCodec PARTICLE_EFFECT_CODEC = PacketCodec.ofRegistry(Registries.PARTICLE_TYPE).andThen( + (buf, type) -> type.getParametersFactory().read((ParticleType) type, buf), + ParticleEffect::getType, + (buf, effect) -> effect.write(buf) ); PacketCodec> OPTIONAL_PARTICLE_EFFECT_CODEC = PARTICLE_EFFECT_CODEC.asOptional(); PacketCodec VECTOR_CODEC = new PacketCodec<>( @@ -39,6 +33,7 @@ public interface ParticleFactoryHelper { @SuppressWarnings("unchecked") static T read(StringReader reader) throws CommandSyntaxException { + reader.expect(' '); return (T)ParticleEffectArgumentType.readParameters(reader, Registries.PARTICLE_TYPE.getReadOnlyWrapper()); } diff --git a/src/main/java/com/minelittlepony/unicopia/particle/SpiralParticleEffect.java b/src/main/java/com/minelittlepony/unicopia/particle/SpiralParticleEffect.java new file mode 100644 index 00000000..d40d9325 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/particle/SpiralParticleEffect.java @@ -0,0 +1,48 @@ +package com.minelittlepony.unicopia.particle; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.particle.ParticleEffect; +import net.minecraft.particle.ParticleType; + +public record SpiralParticleEffect( + WeakTarget centerPoint, + float angularVelocity, + ParticleEffect effect + ) implements ParticleEffect { + @SuppressWarnings("deprecation") + public static final Factory FACTORY = ParticleFactoryHelper.of(SpiralParticleEffect::new, SpiralParticleEffect::new); + + protected SpiralParticleEffect(ParticleType type, StringReader reader) throws CommandSyntaxException { + this(new WeakTarget(reader), + ParticleFactoryHelper.readFloat(reader), + ParticleFactoryHelper.read(reader) + ); + } + + protected SpiralParticleEffect(ParticleType type, PacketByteBuf buf) { + this(new WeakTarget(buf), + buf.readFloat(), + ParticleFactoryHelper.PARTICLE_EFFECT_CODEC.read(buf) + ); + } + + @Override + public ParticleType getType() { + return UParticles.SPIRAL; + } + + @Override + public void write(PacketByteBuf buffer) { + centerPoint.write(buffer); + buffer.writeFloat(angularVelocity); + ParticleFactoryHelper.PARTICLE_EFFECT_CODEC.write(buffer, effect); + } + + @Override + public String asString() { + return null; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java b/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java index 15171015..76d84b03 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java @@ -3,6 +3,8 @@ package com.minelittlepony.unicopia.particle; import java.util.Locale; +import org.jetbrains.annotations.Nullable; + import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -29,9 +31,9 @@ public class TargetBoundParticleEffect implements ParticleEffect { this.targetId = buf.readInt(); } - public TargetBoundParticleEffect(ParticleType type, Entity target) { + public TargetBoundParticleEffect(ParticleType type, @Nullable Entity target) { this.type = type; - this.targetId = target.getId(); + this.targetId = target == null ? -1 : target.getId(); } public int getTargetId() { diff --git a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java index da302010..a474fed3 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java @@ -19,6 +19,7 @@ public interface UParticles { ParticleType RAINBOOM_RING = register("rainboom_ring", FabricParticleTypes.complex(OrientedBillboardParticleEffect.FACTORY)); ParticleType RAINBOOM_TRAIL = register("rainboom_trail", FabricParticleTypes.complex(TargetBoundParticleEffect.FACTORY)); + ParticleType WIND = register("wind", FabricParticleTypes.complex(TargetBoundParticleEffect.FACTORY)); DefaultParticleType RAIN_DROPS = register("rain_drops", FabricParticleTypes.simple()); @@ -26,6 +27,7 @@ public interface UParticles { ParticleType DISK = register("disk", FabricParticleTypes.complex(true, SphereParticleEffect.FACTORY)); ParticleType HEALTH_DRAIN = register("health_drain", FabricParticleTypes.complex(true, FollowingParticleEffect.FACTORY)); + ParticleType SPIRAL = register("spiral", FabricParticleTypes.complex(true, SpiralParticleEffect.FACTORY)); DefaultParticleType GROUND_POUND = register("ground_pound", FabricParticleTypes.simple()); DefaultParticleType CLOUDS_ESCAPING = register("clouds_escaping", FabricParticleTypes.simple(true)); diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java b/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java index ce77a9b8..d6adf192 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java @@ -114,8 +114,12 @@ public class WeatherConditions extends PersistentState implements Tickable { return MathHelper.lerp(interpolation / (float)maxInterpolation, prevWindYaw, windYaw); } + public int getWindInterpolation() { + return interpolation; + } + public Vec3d getWindDirection() { - return Vec3d.fromPolar(0, windYaw).normalize(); + return Vec3d.fromPolar(0, getWindYaw()).normalize(); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java b/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java index 2b19564e..035e1f0d 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia.util; +import java.util.function.Function; import java.util.stream.Stream; import net.minecraft.inventory.Inventory; @@ -22,4 +23,27 @@ public interface InventoryUtil { } return -1; } + + static boolean contentEquals(Inventory a, Inventory b) { + if (a.size() != b.size()) { + return false; + } + for (int i = 0; i < a.size(); i++) { + if (!ItemStack.areEqual(a.getStack(i), b.getStack(i))) { + return false; + } + } + return true; + } + + static I copy(Inventory from, Function factory) { + return copyInto(from, factory.apply(from.size())); + } + + static I copyInto(Inventory from, I into) { + for (int i = 0; i < from.size(); i++) { + into.setStack(i, from.getStack(i).copy()); + } + return into; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java b/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java index a8f27440..2e2c3bd9 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java +++ b/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java @@ -1,10 +1,33 @@ package com.minelittlepony.unicopia.util.serialization; import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; public record PacketCodec(PacketByteBuf.PacketReader reader, PacketByteBuf.PacketWriter writer) { + public static final PacketCodec FLOAT = new PacketCodec<>(PacketByteBuf::readFloat, PacketByteBuf::writeFloat); + public static final PacketCodec INT = new PacketCodec<>(PacketByteBuf::readInt, PacketByteBuf::writeInt); + public static final PacketCodec BYTE = new PacketCodec<>(PacketByteBuf::readByte, (b, v) -> b.writeByte(v)); + public static final PacketCodec LONG = new PacketCodec<>(PacketByteBuf::readLong, PacketByteBuf::writeLong); + public static final PacketCodec STRING = new PacketCodec<>(PacketByteBuf::readString, PacketByteBuf::writeString); + + public static final PacketCodec IDENTIFIER = STRING.xMap(Identifier::new, Identifier::toString); + + public static final PacketCodec ofRegistry(Registry registry) { + return INT.xMap(registry::get, registry::getRawId); + } + + public static final > PacketCodec ofEnum(Supplier valuesGetter) { + final T[] values = valuesGetter.get(); + return INT.xMap(id -> values[MathHelper.clamp(id, 0, values.length)], Enum::ordinal); + } public T read(PacketByteBuf buf) { return reader().apply(buf); @@ -17,4 +40,17 @@ public record PacketCodec(PacketByteBuf.PacketReader reader, PacketByteBuf public PacketCodec> asOptional() { return new PacketCodec<>(buf -> buf.readOptional(reader), (buf, v) -> buf.writeOptional(v, writer)); } + + public PacketCodec xMap(Function to, Function from) { + return new PacketCodec<>(buf -> to.apply(reader.apply(buf)), (buf, v) -> writer.accept(buf, from.apply(v))); + } + + public PacketCodec andThen(BiFunction to, Function from, BiConsumer write) { + return new PacketCodec<>(buf -> { + return to.apply(buf, reader.apply(buf)); + }, (buf, v) -> { + writer.accept(buf, from.apply(v)); + write.accept(buf, v); + }); + } } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 4833e984..18adf7d8 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -360,7 +360,7 @@ "block.unicopia.etched_cloud": "Etched Cloud", "block.unicopia.etched_cloud_slab": "Etched Cloud Slab", "block.unicopia.etched_cloud_stairs": "Etched Cloud Stairs", - "block.unicopia.compacted_etched_cloud": "Cloud Etched Cloud", + "block.unicopia.compacted_etched_cloud": "Etched Cloud", "block.unicopia.cloud_pillar": "Cloud Pillar", "block.unicopia.cloth_bed": "Fancy Cloth Bed", "block.unicopia.cloud_bed": "Cloud Bed", @@ -378,6 +378,7 @@ "entity.unicopia.butterfly": "Butterfly", "entity.unicopia.twittermite": "Twittermite", "entity.unicopia.specter": "Specter", + "entity.unicopia.mimic": "Mimic", "entity.unicopia.cast_spell": "Cast Spell", "entity.unicopia.cast_spell.by": "a spell cast by %s", "entity.unicopia.spellbook": "Spellbook", diff --git a/src/main/resources/assets/unicopia/particles/spiral.json b/src/main/resources/assets/unicopia/particles/spiral.json new file mode 100644 index 00000000..1d45f426 --- /dev/null +++ b/src/main/resources/assets/unicopia/particles/spiral.json @@ -0,0 +1,5 @@ +{ + "textures": [ + "minecraft:heart" + ] +} diff --git a/src/main/resources/assets/unicopia/particles/wind.json b/src/main/resources/assets/unicopia/particles/wind.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/src/main/resources/assets/unicopia/particles/wind.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/main/resources/assets/unicopia/textures/particle/wind.png b/src/main/resources/assets/unicopia/textures/particle/wind.png new file mode 100644 index 00000000..0926e002 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/particle/wind.png differ diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index 855a75b7..46be0255 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -30,6 +30,7 @@ "MixinItemEntity", "MixinItemStack", "MixinLivingEntity", + "MixinLootableContainerBlockEntity", "MixinMilkBucketItem", "MixinMobEntity", "MixinPersistentProjectileEntity",