mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-17 16:52:03 +00:00
Compare commits
810 Commits
8e3fafcc8b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c02aad66a0 | |||
| 076d27a564 | |||
| 00b900d864 | |||
| fe453ab607 | |||
| 52cc478b38 | |||
| 4ea72be581 | |||
| 4560d2de8a | |||
| 4912837dd7 | |||
| aac00f148e | |||
| 52ee181042 | |||
| 4f48f0a6a8 | |||
| 0de97381b7 | |||
| ca70568fad | |||
| 8aa4af3a3a | |||
| 9ab1bec89a | |||
| 14da286e11 | |||
| fc2f5c8669 | |||
| 432e5a11e8 | |||
| 707aea5898 | |||
| 8ba045063d | |||
| 654f20e28f | |||
| 1becfb4f3f | |||
| 61a073099d | |||
| a1f30f6af9 | |||
| 8033d52db3 | |||
| 2702e53068 | |||
| 8b9ae35b43 | |||
| a4b362cb01 | |||
| 0a8f3924d0 | |||
| ccf7d0fbfe | |||
| f628ccfd35 | |||
| d40344a2c0 | |||
| 801fca7cb0 | |||
| 5765532409 | |||
| b8bcc1f3c1 | |||
| 5453e19761 | |||
| 129b0c7ef0 | |||
| 4248e51c60 | |||
| 320ec46ba2 | |||
| f6a87463c5 | |||
| 1dd05c75f1 | |||
| 2c648514ba | |||
| 6983ddd7df | |||
| 7d247bc516 | |||
| 089c127cc3 | |||
| b43ad207c1 | |||
| de263f30e8 | |||
| ad9289fb21 | |||
| ae4a6f937f | |||
| 7d50612abb | |||
| 24620be5c1 | |||
| 4c61b1e28d | |||
| 8810bf7d8f | |||
| 91b3fc938c | |||
| 79ab0d1404 | |||
| 5190cded56 | |||
| f09ef7da8f | |||
| ce17a45afb | |||
| 817572a6ce | |||
| a5cbba8ac9 | |||
| 482d4b0985 | |||
| f7958f3102 | |||
| d64920b0f9 | |||
| e6733bd6cb | |||
| 0acf144f45 | |||
| 61daf139c5 | |||
| 4e1dd0d24e | |||
| 3f3dada35f | |||
| 9a724bf562 | |||
| ea1c65acfb | |||
| 8507472635 | |||
| 759dbe2b65 | |||
| f50264dae6 | |||
| 6632cdace7 | |||
| b5b6bdea95 | |||
| 2050acd63d | |||
| 3b523cf525 | |||
| 854d160f27 | |||
| 25cf1e0cfa | |||
| addcea9fed | |||
| bcc2639a33 | |||
| a4de140424 | |||
| 947fefc95c | |||
| bd93a2657e | |||
| 2463713cde | |||
| aac81c89b6 | |||
| 3e0022ce9c | |||
| 2e9da3032c | |||
| 8c28374eb4 | |||
| cee0163eac | |||
| 4519f2db82 | |||
| 0f1a7403e4 | |||
| bef71e1dcf | |||
| a15368ed29 | |||
| 1976c6eac6 | |||
| eaf7ea8748 | |||
| f8af212e3f | |||
| aa446cfbfe | |||
| 781cbc35aa | |||
| 51f8008a38 | |||
| 1bce603c33 | |||
| cbefa86c5c | |||
| 6ebd6e59c1 | |||
| d59e0017f7 | |||
| 0bca441ca1 | |||
| 670466dbd7 | |||
| 55b88a3aa5 | |||
| 91658fbc13 | |||
| 84d27d82da | |||
| 3e9f3f2c4e | |||
| 4353abfd97 | |||
| f5fd3bb207 | |||
| 5dc659007e | |||
| 19ef84c3cb | |||
| fcb38b3d57 | |||
| 510138eb0a | |||
| b883338396 | |||
| a9b8f53ef1 | |||
| b15d19eec2 | |||
| 9b14109727 | |||
| 870b89d279 | |||
| bff71bfc2a | |||
| bfaf5d40ff | |||
| 8a55c33666 | |||
| 6a9b269798 | |||
| badd0b1b47 | |||
| f7ab77dec5 | |||
| ad6f9cc148 | |||
| fc3a7ea359 | |||
| ffc7856993 | |||
| 807aaa4478 | |||
| 3bd2387dc3 | |||
| edd78d2548 | |||
| bda3fc5aff | |||
| e9ed8923c9 | |||
| 56e8e2554c | |||
| 75f9026ea1 | |||
| e376e1e80d | |||
| 2bdf876b47 | |||
| dac414274e | |||
| c580e46125 | |||
| 03bc723b70 | |||
| a10c6d47f9 | |||
| 3985b74bcf | |||
| ec0bc362cd | |||
| d819658ace | |||
| 4d8638e62f | |||
| 82691eaf80 | |||
| 07fd0fde44 | |||
| 889e30a434 | |||
| 27017043f9 | |||
| 429fb87c08 | |||
| fd1a4d9577 | |||
| 17e6a9bc20 | |||
| 87d43302e1 | |||
| 73e5d64ba2 | |||
| 7534d8436c | |||
| 1f05f6d524 | |||
| cfc82bcea5 | |||
| 1e5825b096 | |||
| c08c761af3 | |||
| 0c4ad6a5aa | |||
| 277ad4bf98 | |||
| 0ce2a49419 | |||
| a60c1a097e | |||
| f491725144 | |||
| 0f1864f8bc | |||
| 30a5a89164 | |||
| 6ac2d0fbb1 | |||
| fa2c93ec06 | |||
| 47aa196b59 | |||
| 51f6e19339 | |||
| 9240cd4b27 | |||
| 950e879861 | |||
| d3a457d8d0 | |||
| eb3b685af0 | |||
| 3151e54ab3 | |||
| 96f311c129 | |||
| 836cedec3c | |||
| 614b87f7e1 | |||
| 30a22c366b | |||
| 3eb47bb9d3 | |||
| 701d7a9a2e | |||
| bfeb822279 | |||
| 2ff348adf0 | |||
| d4bde67e0f | |||
| 0666d8d308 | |||
| e83f84f527 | |||
| b01db32908 | |||
| caa7e3e793 | |||
| bd617dbf06 | |||
| ff80ab46b7 | |||
| 84372204fd | |||
| 5630c5c8dc | |||
| 52e3d73678 | |||
| ad59c5f567 | |||
| 957bb65a26 | |||
| 455055c67c | |||
| da92be73ef | |||
| 515b06d97c | |||
| b110bfc5f7 | |||
| b4d2dd8a9f | |||
| bb9f25ee8d | |||
| 81346ac149 | |||
| e1abac1271 | |||
| aa97259165 | |||
| 9cc68d7707 | |||
| 6366b5e1f6 | |||
| d04523accd | |||
| 05e8a1b398 | |||
| e56850a236 | |||
| 066aee96cc | |||
| e78f805c92 | |||
| 34cc994598 | |||
| 398d6ade61 | |||
| 2e1c010fa2 | |||
| 95e82cc719 | |||
| 9a72dd5eab | |||
| fc000e71d5 | |||
| 4a1a6f6697 | |||
| 72e8d27633 | |||
| 55706ba9ea | |||
| 71935e61b5 | |||
| 25236cc0a1 | |||
| 4b243f1a47 | |||
| 181a928c87 | |||
| c4c2ada1f6 | |||
| 76c1c3d600 | |||
| 7c044b7094 | |||
| e3fad7064b | |||
| c65cd2d17e | |||
| 3317574bf2 | |||
| bf396afcaf | |||
| b72a7cf843 | |||
| 063dff463d | |||
| 00d6b1edd3 | |||
| a57b582cf1 | |||
| b33f681ffa | |||
| b1783abdce | |||
| 88eb510dc3 | |||
| 8129bb2571 | |||
| 6042c3b154 | |||
| 9b76d47c52 | |||
| e934bbfe44 | |||
| e376675fa6 | |||
| 7170400f49 | |||
| 4c1ed87344 | |||
| 7b2f9c26f4 | |||
| 9004800326 | |||
| 533ac1afab | |||
| dc901f5951 | |||
| 67da422ea6 | |||
| 0bf33e7fe8 | |||
| 20f376f713 | |||
| d0c96422b2 | |||
| 65f0ad5dfd | |||
| 98ff2aed81 | |||
| 0787702f23 | |||
| f1b9527d3f | |||
| c5f6c95ca5 | |||
| d1bcb51225 | |||
| f1b8366531 | |||
| d85c8b8194 | |||
| 6ef55affcb | |||
| 9ae01ecc5a | |||
| 8bd1455a2c | |||
| 315c524c42 | |||
| 75cb8faac4 | |||
| cc12260225 | |||
| 1d926e705b | |||
| 4e88ea80f3 | |||
| b24f9fc448 | |||
| 8c2dc66c7c | |||
| 334d8a26ac | |||
| 74e50caf70 | |||
| d15d16d593 | |||
| ec82367c50 | |||
| a6d2bdf8bd | |||
| bae7458668 | |||
| b7642ad2be | |||
| 3dbecb0bd5 | |||
| a439a1059b | |||
| 80d6ee8cf5 | |||
| 69a8402f11 | |||
| 5812386676 | |||
| e62909d457 | |||
| 12b9712a8d | |||
| b4ea50d76a | |||
| bdb3c8f4a7 | |||
| 498f219ab9 | |||
| c0861bfcad | |||
| 68e76b5feb | |||
| 863cf61f1a | |||
| 802e5fc96f | |||
| f3b56f70e3 | |||
| 9ec707674a | |||
| 207ab47866 | |||
| dad26a164c | |||
| 04b5d3b29d | |||
| d081a72b9a | |||
| f096e8ff0b | |||
| 79a6092b28 | |||
| aa93aeec95 | |||
| 1e1db3525e | |||
| 544f76c502 | |||
| d588898f5d | |||
| 939b7a9584 | |||
| 520613aab0 | |||
| 330f99bb0e | |||
| 79318b56bd | |||
| fe0a0b72a8 | |||
| 4627dd58fb | |||
| c8c363e667 | |||
| 6237a25a69 | |||
| 801a8487f5 | |||
| 3168dd9897 | |||
| 01bb9a9431 | |||
| 57fe6f4acc | |||
| a1ae36a54c | |||
| 8a9972b6fc | |||
| f7657ad9ad | |||
| 6edaedb50d | |||
| 521b8122ac | |||
| 8fa53b778c | |||
| e5c4e0b89a | |||
| 77c04a57a9 | |||
| 80a47d850e | |||
| bf160e4237 | |||
| fc2faf9723 | |||
| d8245047a9 | |||
| c0a1ef94f0 | |||
| 21c57a6d22 | |||
| 4cad0f0cce | |||
| 4c2ade3930 | |||
| 14e1e62deb | |||
| 9b26f867bc | |||
| fd18cf6ac0 | |||
| 4278c04d60 | |||
| 0bad9e07db | |||
| 8b76f80b5c | |||
| 270ab00c52 | |||
| d9e806ed27 | |||
| 17bd7768eb | |||
| bf64cce947 | |||
| caece00e7f | |||
| dbdfaa5d9e | |||
| 42ac34ca70 | |||
| ea6939e18d | |||
| 63eb43d50a | |||
| e9d9f59a6a | |||
| 4eaa3304c3 | |||
| 1490ff7fcf | |||
| 3817df2a12 | |||
| 06a4ea0a02 | |||
| 66d114bfe3 | |||
| 6d3490da14 | |||
| e23243ff56 | |||
| 67609d71c0 | |||
| 7e5cfdeceb | |||
| 99df7e20e0 | |||
| d937cb153c | |||
| 4fb7ddfecf | |||
| 9dc1cc2d6f | |||
| 013eb0778b | |||
| 25fdc2c2c8 | |||
| 55c46f56ec | |||
| 6f7d61eafb | |||
| 03ff8e485e | |||
| e0ba6c6ead | |||
| 7807b9706b | |||
| 11599889fa | |||
| 2d750cbcab | |||
| d882469c79 | |||
| 7a3d520608 | |||
| dd80b52a11 | |||
| 388300c553 | |||
| eebab1bc19 | |||
| a70a0ebc74 | |||
| febc69bdef | |||
| 8b5b5333c6 | |||
| 55e07648cf | |||
| 0023bdab6e | |||
| 931e51a647 | |||
| 4bbaf0976f | |||
| e729570010 | |||
| b1966a3423 | |||
| 7803e1ec7c | |||
| def60169a8 | |||
| 25cc182793 | |||
| e170bcf0ef | |||
| d0952adaf9 | |||
| bdc89303b4 | |||
| 18538e833b | |||
| fe9f6f3cb5 | |||
| 18888ed520 | |||
| 4d83ec1525 | |||
| 58508bd87c | |||
| e7c835af52 | |||
| 6caf719158 | |||
| 5b80c97337 | |||
| bd26aa3dc4 | |||
| 9d210d254a | |||
| c8a542a3af | |||
| eadaa7b54e | |||
| 414df530f0 | |||
| 5234390931 | |||
| eb8eecf080 | |||
| 3aad68a6e6 | |||
| e25d61d80e | |||
| 66a1ee8007 | |||
| 172775c141 | |||
| 4b01b7fc88 | |||
| 68d832f7bc | |||
| d8ea2f4ac7 | |||
| 82bb9baec6 | |||
| 9848f4b92d | |||
| aeb37a4c6e | |||
| afcb01d155 | |||
| c5678ce877 | |||
| 76aad61191 | |||
| 467c25b20c | |||
| 0cdb60c0d9 | |||
| 23134796ee | |||
| fec4f91672 | |||
| 5b40bf9066 | |||
| 63f4f11aa8 | |||
| f520584368 | |||
| eef7cf28da | |||
| 30b4560bd5 | |||
| 1eb6e8f9f4 | |||
| 3ae2daec88 | |||
| 7f7516d9a0 | |||
| 3533e8ae6c | |||
| fe246bf54e | |||
| 4b82347d43 | |||
| 60fe2caf8f | |||
| 161d2cdceb | |||
| a4bc8cdac8 | |||
| 0be51ab902 | |||
| a16db39884 | |||
| d258f17852 | |||
| d77563147c | |||
| fcc5d82763 | |||
| 6b3db98327 | |||
| 69f1ef6be1 | |||
| 99680dbf6f | |||
| 585056ccc3 | |||
| e1807d2492 | |||
| 3e94960f0a | |||
| 222bbb5b84 | |||
| fddbebb33b | |||
| 62c1478913 | |||
| 66a67c931d | |||
| 550d73f1d7 | |||
| 47550aefae | |||
| 5106ac64da | |||
| 39ebfce1bb | |||
| 2379c154a5 | |||
| 5265b1738b | |||
| 4d3a8cc217 | |||
| 8253d772cb | |||
| a2a7107dd7 | |||
| b3284d4bd6 | |||
| ae164e625a | |||
| 9fe6c57b5d | |||
| 1a43a342ea | |||
| 59cf29949f | |||
| b2d68d93cf | |||
| 068b66b234 | |||
| e37a519ef8 | |||
| a45d2ddbb7 | |||
| 4c131db2ca | |||
| 948d603c0e | |||
| 9263133e64 | |||
| 3b208ae860 | |||
| b47c7dc3fa | |||
| b1314074a8 | |||
| 52a96de8d6 | |||
| 72d4a604c2 | |||
| 90126a4be7 | |||
| b2ee9ec92d | |||
| 0551fb0b1f | |||
| 6253b620a9 | |||
| faede927fc | |||
| 307828ff7f | |||
| 7b6e22292a | |||
| 4b5e40bff7 | |||
| bf4da9fd39 | |||
| 229ded8613 | |||
| 4dcf98cbb0 | |||
| 02a11704ce | |||
| 91e536425d | |||
| 0f1cc17af6 | |||
| 236cca30ba | |||
| 846663e320 | |||
| 726fe1ce2e | |||
| 88b5ffedb2 | |||
| 79e66c7a95 | |||
| 8d1b407bb0 | |||
| 77c01dc531 | |||
| cd30098eca | |||
| caa875f15c | |||
| 58e8a5c523 | |||
| bc7136b2cb | |||
| e13a64756f | |||
| 63332435d2 | |||
| 4e66c5758c | |||
| 723f8aea5d | |||
| c0d1c33f54 | |||
| 3dde0ffb79 | |||
| 0364d3f172 | |||
| 1902084523 | |||
| bbabd259b6 | |||
| 217ae263cd | |||
| 3b5f50023b | |||
| 518df3a73a | |||
| 893db17c03 | |||
| 7ce28559d2 | |||
| b1b4608dd8 | |||
| e702661388 | |||
| dfe8d46d99 | |||
| 9c6d48ae2c | |||
| 81e68bb5fa | |||
| 2b8bce5e4b | |||
| 9810ee543f | |||
| a0195987bd | |||
| 698413cd91 | |||
| ecc78b7a2a | |||
| a616b7637e | |||
| 7c4b64b50a | |||
| 637b522486 | |||
| 864e4e8aaf | |||
| b04f3318b9 | |||
| cc2cd80a01 | |||
| 1793eb7621 | |||
| 7643dbc439 | |||
| cf212d94f9 | |||
| 997175c753 | |||
| 6c0163c3ec | |||
| b79d358db9 | |||
| 07fe144b2f | |||
| f5d9c08468 | |||
| e3119bf77c | |||
| 4264a2bbd3 | |||
| d864ce2475 | |||
| 15fdd989e2 | |||
| 5b8638e15e | |||
| f3e7538fb7 | |||
| af8592082e | |||
| 62ee3f2777 | |||
| 835233672f | |||
| 8d4a0640e2 | |||
| bcc12aad19 | |||
| 4ffa1543cc | |||
| 3aa0f5b9c2 | |||
| 964094f65a | |||
| c51d5ee369 | |||
| 6d837b2bf3 | |||
| 4f13b4eca9 | |||
| b946f78d1f | |||
| ca17265bf1 | |||
| d235342889 | |||
| 706ac9bd2c | |||
| 5de72b0636 | |||
| 295be3d26a | |||
| 3af7ed077c | |||
| e364a7dd06 | |||
| d06a67ade6 | |||
| 6d94366693 | |||
| cb06300685 | |||
| 08eb9f5842 | |||
| e1f512c94e | |||
| 6320b982a8 | |||
| 46fe696a58 | |||
| dffc2812e1 | |||
| 614ec9cda9 | |||
| 17f588b95b | |||
| 7c949d8679 | |||
| ba1887fe93 | |||
| b83e030a44 | |||
| 5fcdb256b3 | |||
| b69291a7d3 | |||
| f01b1c3cbb | |||
| b99f63afb7 | |||
| 04865abb39 | |||
| d551ff9cce | |||
| 24d6a547d6 | |||
| 3649620726 | |||
| 23c1c3d231 | |||
| 72944a03e9 | |||
| dab647e514 | |||
| 9e4609d5b3 | |||
| 6bafdc7794 | |||
| f2b72394de | |||
| ec896b27ac | |||
| 06881f05ca | |||
| 624d1c6b78 | |||
| ff725e1ffa | |||
| 5714829c13 | |||
| 866efa0d9b | |||
| e51dc765a2 | |||
| 818f8e89cd | |||
| 23e3dd18f9 | |||
| 0699b14d4e | |||
| 7a1668c902 | |||
| b5e9dff33c | |||
| a5cbc977b5 | |||
| 3d6906b981 | |||
| 9f28c02488 | |||
| 75f485667b | |||
| 9708bfafa3 | |||
| 12bc4d7e8a | |||
| 3d3024ae8a | |||
| cf73074e27 | |||
| 2325313ad2 | |||
| 10040f7339 | |||
| caa9f44ab6 | |||
| 9d8a8080ba | |||
| b290f49143 | |||
| 5bbb319161 | |||
| 86a597f0f5 | |||
| 14698d4ccb | |||
| aa049aaff4 | |||
| 7782d63660 | |||
| 7b150ef72e | |||
| 39a41c016c | |||
| 4533da34e0 | |||
| e83bb4fa0c | |||
| 602fa297d0 | |||
| 04691aaa48 | |||
| 03f49e59c8 | |||
| fde098c6c9 | |||
| 2f6e7b1201 | |||
| 0db21b20a6 | |||
| 6fc2efacc6 | |||
| 24b99ff8f9 | |||
| 88591e031f | |||
| 0e685edcf8 | |||
| a4f60e5b40 | |||
| 48445839d2 | |||
| dadfdb2903 | |||
| 8becffd579 | |||
| 89b1790728 | |||
| 0b6679e84b | |||
| 62ff85e75c | |||
| ad2bda4ad7 | |||
| 48bc9fd13b | |||
| 24534459b1 | |||
| 08ab782a9a | |||
| 9fe95ee1b2 | |||
| 6961b672da | |||
| 63c2cea29e | |||
| d77cdccaab | |||
| ff7d1579cb | |||
| ea756bd5ba | |||
| 033a774c92 | |||
| aeeb730636 | |||
| 9fffaf866a | |||
| 34f883cf46 | |||
| 376d2388c5 | |||
| 84e0ebf771 | |||
| 8f7f672a15 | |||
| 7da5b8316f | |||
| f08ecbe651 | |||
| 76e74aca62 | |||
| 4d78e89e95 | |||
| 2a00411b35 | |||
| 9e7b93af2b | |||
| 4f3cf56e28 | |||
| 5260124274 | |||
| 8e5eea4f46 | |||
| 052b47c04f | |||
| 739c4b09d5 | |||
| 6a34eefceb | |||
| 722ad7cc85 | |||
| b8e2a7b57a | |||
| f5b0a79cea | |||
| 25560a65b5 | |||
| be1429e99e | |||
| 651c048115 | |||
| d1ccf4dbc7 | |||
| 677fff2c82 | |||
| dd1165c805 | |||
| aa8d895b14 | |||
| 7174ff7cf9 | |||
| dcf618dda1 | |||
| 6f7536f03c | |||
| c56f5ed1a3 | |||
| 005cd0c66e | |||
| c6f0f2084f | |||
| 837deedc08 | |||
| 686b115b88 | |||
| d617c8032c | |||
| 11b41ab562 | |||
| d229bccd19 | |||
| 341a67f6da | |||
| 33952b3cd0 | |||
| 57a298dc06 | |||
| 7e217df898 | |||
| 0285116ddc | |||
| 0223b55de8 | |||
| e0a1cbaf6d | |||
| b7f646ba48 | |||
| 8fa40769a8 | |||
| 0a7eb0b742 | |||
| 8bd65290fc | |||
| 28eb45338a | |||
| 78ef0cb1f8 | |||
| 8d4078989c | |||
| 9fd1764522 | |||
| a370b15020 | |||
| f8641836df | |||
| 9f78d37c79 | |||
| 401d882ff0 | |||
| 9bdeb73fa7 | |||
| 3049c42867 | |||
| 18fdb0f232 | |||
| a02e0770af | |||
| 661c588fef | |||
| 0f7603d3cb | |||
| cab2fa6391 | |||
| 0e046aa9f1 | |||
| 1811e9333e | |||
| 8551d9e835 | |||
| 6198dc67fc | |||
| 884b7799a0 | |||
| 5193776d86 | |||
| c96325144a | |||
| fa9590d9c3 | |||
| ff2e40198f | |||
| c75e142c8a | |||
| 96ff503fa8 | |||
| 7b086fbd8f | |||
| 0603452264 | |||
| b01d5a68d9 | |||
| bb56f69edc | |||
| fbb783b947 | |||
| 08a0f74391 | |||
| b285d012dc | |||
| c7cd740f23 | |||
| a836a96d13 | |||
| e62d844c1d | |||
| df3e168b31 | |||
| 66b6215fdd | |||
| 02df09e40d | |||
| 6f5ce488a9 | |||
| 73fb83a0da | |||
| 3257f5cbcc | |||
| 11d81a910e | |||
| 1eb5a3188d | |||
| 50c2014361 | |||
| 1c08ab3566 | |||
| 3fdf270aa9 | |||
| 171cd4a840 | |||
| 1af3e0410d | |||
| 4507b6dc1b | |||
| 20f6be7e28 | |||
| 5fe53b2406 | |||
| 62dc0a2981 | |||
| f62ddde369 | |||
| fa53d72f63 | |||
| e5a0df63c5 | |||
| be1bf37e0b | |||
| 1615791ee0 | |||
| e22b4814f5 | |||
| 23b2cd19df | |||
| 3fd1c00895 | |||
| 0759e1514b | |||
| f03037a612 | |||
| 6084991f1c | |||
| f41702e7ea | |||
| efda6c1a05 | |||
| 259b2adc07 | |||
| 8bc16ee048 | |||
| a6c2ed6177 | |||
| 0d29fc44e0 | |||
| cb3732fcb7 | |||
| bca08d266e | |||
| c998d2271a | |||
| 01dbfc7ede | |||
| 4408ec3ecd | |||
| ac317cd2e1 | |||
| 09ea8d6fbc | |||
| a9b7b450d5 | |||
| 93f90ca6de | |||
| 2533827cf8 | |||
| a0b527b8cb | |||
| 31aa5cae8c | |||
| f7d03b1e74 | |||
| 390bd18be7 | |||
| 33b2c23f13 | |||
| 653afe4040 | |||
| 28109756dd | |||
| 4e0efec612 | |||
| 580222c139 | |||
| 407a916b40 | |||
| f4dfc43d04 | |||
| 1afdc301ab | |||
| 2de3085f13 | |||
| fda784d50d | |||
| 0e42182cce | |||
| fca6a46200 | |||
| cf4859e61c | |||
| 8c15c466ef | |||
| dd2b3c570b | |||
| bc644e0305 | |||
| 44b1384a97 | |||
| ffa9017cbd | |||
| 7e67998a23 | |||
| 523a26aa60 |
@@ -0,0 +1,23 @@
|
|||||||
|
name: Agones Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- Dockerfile
|
||||||
|
- Makefile
|
||||||
|
- deploy/k8s/**
|
||||||
|
- scripts/verify_kind_agones.sh
|
||||||
|
- .github/workflows/agones-integration.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
kind-agones:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Verify disposable kind/Agones lifecycle
|
||||||
|
run: make verify-kind-agones
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: Allocated Compose Smoke
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- Dockerfile
|
||||||
|
- Makefile
|
||||||
|
- compose.allocated-smoke.yml
|
||||||
|
- server/api/**
|
||||||
|
- server/store/**
|
||||||
|
- server/workload/**
|
||||||
|
- server/migrations/**
|
||||||
|
- scripts/verify_allocated_compose.sh
|
||||||
|
- .github/workflows/allocated-compose.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
allocated-compose:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Verify independent allocated Compose flow
|
||||||
|
run: make verify-allocated-compose
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
name: Dedicated Server Smoke Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dedicated-server-smoke:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Build and verify exported dedicated server
|
||||||
|
run: make verify-phase6
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
name: ENet Integration Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
enet-integration:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Build the pinned Godot test image
|
||||||
|
run: docker build --target enet-test -t cosmic-clash-enet-tests .
|
||||||
|
- name: Run multi-process ENet smoke tests
|
||||||
|
run: docker run --rm cosmic-clash-enet-tests bash scripts/verify_enet_integration.sh
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: Multiplayer Chaos Recovery
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- Dockerfile
|
||||||
|
- Makefile
|
||||||
|
- compose.chaos-smoke.yml
|
||||||
|
- server/cmd/maintenance/**
|
||||||
|
- server/store/**
|
||||||
|
- server/migrations/**
|
||||||
|
- scripts/verify_chaos_recovery.sh
|
||||||
|
- .github/workflows/multiplayer-chaos.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
api-restart-recovery:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Verify API restart and stalled-allocation recovery
|
||||||
|
run: make verify-chaos-recovery
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: Multiplayer API Load
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- server/api/**
|
||||||
|
- server/domain/**
|
||||||
|
- server/matcher/**
|
||||||
|
- Makefile
|
||||||
|
- .github/workflows/multiplayer-load.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
api-load:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Verify 10,000-client API load boundary
|
||||||
|
run: make verify-multiplayer-load
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# The Go control plane is ~24k lines, and until this workflow existed the only
|
||||||
|
# Go tests CI ever ran were the two load tests in multiplayer-load.yml. Nothing
|
||||||
|
# else — domain policy, the wire/store boundaries, the allocator, the Steam
|
||||||
|
# adapter — gated a change. The Godot unit suite is covered (verify-phase6 runs
|
||||||
|
# test_runner.tscn as its first step); this closes the equivalent gap on the
|
||||||
|
# Go side.
|
||||||
|
#
|
||||||
|
# Deliberately Docker-free and cluster-free so it stays fast enough to gate
|
||||||
|
# every push. Tests that need a real PostgreSQL or Redis are behind the
|
||||||
|
# `integration` build tag and stay with their own scripts; `go vet` is still
|
||||||
|
# run over that tag so those files cannot rot uncompiled.
|
||||||
|
name: Server Unit Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
go-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: server
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: server/go.mod
|
||||||
|
cache-dependency-path: server/go.sum
|
||||||
|
- name: Build
|
||||||
|
run: go build ./...
|
||||||
|
- name: Vet
|
||||||
|
run: go vet ./...
|
||||||
|
# Integration-tagged files are excluded from the default build, so
|
||||||
|
# without this a signature change could leave them broken until someone
|
||||||
|
# ran the integration scripts by hand.
|
||||||
|
- name: Vet integration-tagged tests
|
||||||
|
run: go vet -tags integration ./...
|
||||||
|
- name: Test
|
||||||
|
run: go test ./...
|
||||||
|
# The control plane is concurrent by design: outbox dispatchers, the
|
||||||
|
# event hub, the matcher worker and the allocator all run in parallel.
|
||||||
|
- name: Test with race detector
|
||||||
|
run: go test -race ./...
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: Supply Chain Policy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
repository-policy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Verify immutable image references and secret hygiene
|
||||||
|
run: make verify-supply-chain
|
||||||
|
- name: Verify release process is documented
|
||||||
|
run: test -s docs/SUPPLY-CHAIN.md
|
||||||
+23
@@ -5,3 +5,26 @@
|
|||||||
training/.venv/
|
training/.venv/
|
||||||
training/smoke_run.log
|
training/smoke_run.log
|
||||||
training/__pycache__/
|
training/__pycache__/
|
||||||
|
|
||||||
|
# Intermediate PPO checkpoints: only final.zip is ever committed (see
|
||||||
|
# run_training.sh) — --resume only ever points at final.zip, and a single
|
||||||
|
# experiment's intermediate checkpoints were 2401 files / ~500MB, of which
|
||||||
|
# final.zip was ~0.2MB. This is the training-results-survive-any-machine
|
||||||
|
# property from ~2500x less data, not a relaxation of it.
|
||||||
|
training/checkpoints/*/ppo_*_steps.zip
|
||||||
|
|
||||||
|
# Exported training binary: a regenerable build artifact (rebuilt by
|
||||||
|
# export_linux.sh / run_training.sh), not a training result.
|
||||||
|
training/build/
|
||||||
|
|
||||||
|
# Exported dedicated server binary (task 6.1): same reasoning — an 85MB
|
||||||
|
# regenerable artifact, rebuilt by `godot --headless --path Game
|
||||||
|
# --export-release "Linux Dedicated Server"`.
|
||||||
|
server/build/
|
||||||
|
|
||||||
|
# Steam exports and local App ID configuration are developer-machine inputs.
|
||||||
|
steam/build/
|
||||||
|
steam_appid.txt
|
||||||
|
|
||||||
|
# Texture generator scripts: throwaway env, not the scripts themselves.
|
||||||
|
tools/textures/.venv/
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
[submodule "mcp/godot-mcp"]
|
[submodule "mcp/godot-mcp"]
|
||||||
path = mcp/godot-mcp
|
path = mcp/godot-mcp
|
||||||
url = https://github.com/tugcantopaloglu/godot-mcp.git
|
url = https://github.com/tugcantopaloglu/godot-mcp.git
|
||||||
|
[submodule "mcp/blender-mcp"]
|
||||||
|
path = mcp/blender-mcp
|
||||||
|
url = https://github.com/ahujasid/blender-mcp.git
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"GODOT_PATH": ""
|
"GODOT_PATH": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blender": {
|
||||||
|
"command": "uv",
|
||||||
|
"args": ["run", "--project", "mcp/blender-mcp", "blender-mcp"],
|
||||||
|
"env": {
|
||||||
|
"DISABLE_TELEMETRY": "1",
|
||||||
|
"BLENDER_MCP_DISABLE_TELEMETRY": "1",
|
||||||
|
"MCP_DISABLE_TELEMETRY": "1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# Investigate and fix the failing Agones Integration CI gate
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
`make verify-kind-agones` (workflow `.github/workflows/agones-integration.yml`,
|
||||||
|
script `scripts/verify_kind_agones.sh`) fails. Find the root cause and fix it so
|
||||||
|
the gate passes on CI. Repo: `jcreek/CosmicClash`, branch `feat/multiplayer`,
|
||||||
|
PR #30.
|
||||||
|
|
||||||
|
## What is already known — do not re-derive this
|
||||||
|
|
||||||
|
**The failure.** `helm upgrade --install agones ... --wait --timeout 5m` fails
|
||||||
|
with `Error: context deadline exceeded`. Immediately before, Helm reports:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource Deployment/agones-system/agones-controller not ready. status: InProgress, message: Available: 0/1
|
||||||
|
resource Deployment/agones-system/agones-extensions not ready. status: InProgress, message: Available: 0/1
|
||||||
|
resource Deployment/agones-system/agones-allocator not ready. status: InProgress, message: Available: 0/1
|
||||||
|
```
|
||||||
|
|
||||||
|
So the cluster is created, the game-server image loads, and the Agones chart
|
||||||
|
installs — but none of its Deployments become Available inside 5 minutes. The
|
||||||
|
script never reaches the parts that exercise this repo's own manifests.
|
||||||
|
|
||||||
|
**It is pre-existing.** It fails identically at `089c127c`, the branch head
|
||||||
|
before recent work. It is not caused by the branch's changes. Do not assume a
|
||||||
|
recent commit broke it.
|
||||||
|
|
||||||
|
**It is not architecture-specific.** It fails the same way on GitHub's
|
||||||
|
`ubuntu-24.04` amd64 runners and on an arm64 macOS developer machine. Agones
|
||||||
|
1.49.0 publishes both amd64 and arm64 images.
|
||||||
|
|
||||||
|
**It is not a Helm kubeVersion rejection.** Agones charts 1.49.0, 1.50.0 and
|
||||||
|
1.51.0 declare no `kubeVersion` constraint, so Helm is not refusing the
|
||||||
|
Kubernetes version — the pods are being created and are not becoming ready.
|
||||||
|
|
||||||
|
**Ruled out as a red herring:** reproducing locally on a machine with heavy
|
||||||
|
Docker usage produced `FailedCreatePodSandBox: containerd connection reset`,
|
||||||
|
which is local resource pressure, not the CI cause. If you see that locally,
|
||||||
|
clear Docker state and retry rather than chasing it.
|
||||||
|
|
||||||
|
**There may be two distinct failures, not one.** After `docker system prune`,
|
||||||
|
a local run got *past* the Agones install cleanly (controller and allocator
|
||||||
|
both reached "condition met") and failed later, at:
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/verify_kind_agones.sh:146
|
||||||
|
kubectl wait --for=jsonpath='{.status.ready}'=2 fleet/cosmic-clash-game -n cosmic-clash --timeout=5m
|
||||||
|
error: timed out waiting for the condition on fleets/cosmic-clash-game
|
||||||
|
```
|
||||||
|
|
||||||
|
So locally the Agones install is fine and the **Fleet's game-server pods never
|
||||||
|
become Ready**; on CI the run never gets that far because the Agones install
|
||||||
|
itself times out. Treat these as potentially separate problems: fixing the CI
|
||||||
|
Agones timeout may simply expose the Fleet one underneath. Both need to pass.
|
||||||
|
|
||||||
|
The Fleet failure is the more suspicious of the two for recent work, because
|
||||||
|
`deploy/k8s/base/fleet.yaml` changed: the join-signing key material moved from
|
||||||
|
a single raw-bytes secret key (`join-signing-key`) to a JSON map
|
||||||
|
(`join-signing-keys.json`), and the mount's `items[].key` moved with it. The
|
||||||
|
script's `kubectl create secret` was updated to match and does succeed
|
||||||
|
(`secret/cosmic-clash-game-server created`), so the obvious mismatch is not
|
||||||
|
present -- but verify the pod actually mounts and starts rather than assuming.
|
||||||
|
Note the script's `sed` also strips `--allocated-mode` and the roster path and
|
||||||
|
blanks `--control-plane-url`, so the game server runs in a reduced mode here;
|
||||||
|
check whether it is failing for a reason unrelated to the key at all.
|
||||||
|
|
||||||
|
## Pinned versions (all in `scripts/verify_kind_agones.sh`)
|
||||||
|
|
||||||
|
| Thing | Value | Override |
|
||||||
|
|---|---|---|
|
||||||
|
| Agones chart | `1.49.0` | `AGONES_VERSION` |
|
||||||
|
| kind node image | `kindest/node:v1.33.1` (Kubernetes 1.33) | `KIND_NODE_IMAGE` |
|
||||||
|
| Cluster | single node, `--wait 120s` | `KIND_CLUSTER_NAME` |
|
||||||
|
| Runner | `ubuntu-latest` (ubuntu-24.04), 30 min timeout | — |
|
||||||
|
|
||||||
|
The chart is installed with `--set agones.controller.replicas=1`,
|
||||||
|
`agones.extensions.replicas=1`, `agones.allocator.replicas=1`, and
|
||||||
|
`agones.extensions.resources.{requests,limits}.ephemeral-storage` lowered to
|
||||||
|
128Mi/512Mi. That ephemeral-storage override already exists because Agones 1.49
|
||||||
|
otherwise requests 10,100 MiB and will not schedule on a default kind node —
|
||||||
|
there is a comment saying so. **A similar resource-fit problem for the other
|
||||||
|
Deployments is a strong hypothesis worth checking first.**
|
||||||
|
|
||||||
|
## Diagnostics are already in place
|
||||||
|
|
||||||
|
The script now dumps, on any failure and before the cluster is deleted: node
|
||||||
|
capacity and conditions, pods in `agones-system` and `cosmic-clash`, recent
|
||||||
|
events per namespace, and describe + current/previous logs for every not-ready
|
||||||
|
pod. Set `KIND_KEEP_ON_FAILURE=1` to retain the cluster for interactive
|
||||||
|
inspection instead of deleting it.
|
||||||
|
|
||||||
|
Its first run revealed a bug in the diagnostics themselves: a
|
||||||
|
`kubectl cluster-info` reachability guard suppressed the entire dump. That
|
||||||
|
guard has been removed, so the dump now always runs on failure.
|
||||||
|
|
||||||
|
**Start by reading that output**, either from a CI run or a local run. The most
|
||||||
|
likely candidates it will distinguish between:
|
||||||
|
|
||||||
|
1. **Resource pressure** — `FailedScheduling ... Insufficient cpu/memory/
|
||||||
|
ephemeral-storage`. Fix by lowering requests for the other Deployments the
|
||||||
|
way extensions already is, or by giving the kind cluster more capacity.
|
||||||
|
2. **Version incompatibility** — Agones 1.49 against Kubernetes 1.33. Check
|
||||||
|
Agones' release notes for its supported Kubernetes range; if 1.33 is outside
|
||||||
|
it, either raise `agones_version` or lower `kind_node_image`. Confirm the
|
||||||
|
pairing is one Agones actually tests.
|
||||||
|
3. **Probe/readiness failure** — pods Running but never Ready. The pod logs and
|
||||||
|
describe output will show the failing probe.
|
||||||
|
4. **Image pull** — `ImagePullBackOff` on an Agones image.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- **Do not weaken the gate to make it pass.** Removing `--wait`, extending the
|
||||||
|
timeout to hide a real failure, or `|| true` around the install are all wrong.
|
||||||
|
If the cause is genuinely a timeout on slow-but-working startup, raising it
|
||||||
|
is acceptable *only* with evidence that the pods do become Available, and the
|
||||||
|
new value should be justified in a comment.
|
||||||
|
- Keep it a disposable, isolated cluster: it must not touch an existing cluster,
|
||||||
|
and the EXIT trap must still remove the one it created.
|
||||||
|
- If you change a pinned version, pin the new one explicitly and say why in the
|
||||||
|
commit message. Do not float to `latest`.
|
||||||
|
- `CLAUDE.md` applies: never create co-authored commits, never mention Claude.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `make verify-kind-agones` passes locally (needs Docker, kind, kubectl, Helm).
|
||||||
|
- The `Agones Integration` workflow passes on PR #30. It is `pull_request`
|
||||||
|
triggered with path filters on `Dockerfile`, `Makefile`, `deploy/k8s/**`,
|
||||||
|
`scripts/verify_kind_agones.sh`, and its own workflow file — so a change to
|
||||||
|
the script will trigger it.
|
||||||
|
- Do not regress the other seven workflows. `Allocated Compose Smoke` was also
|
||||||
|
failing and has just been fixed; confirm it stays green.
|
||||||
|
|
||||||
|
## Useful context
|
||||||
|
|
||||||
|
- `multiplayer-next.md` §7 task 8.49 describes what this gate is meant to prove.
|
||||||
|
- `deploy/k8s/base/fleet.yaml` is the Fleet the script applies after Agones is
|
||||||
|
up, with a `sed` that swaps the release digest placeholder for the locally
|
||||||
|
built image and strips `--allocated-mode` and the roster path (there is no
|
||||||
|
control plane in this disposable cluster).
|
||||||
|
- The gate is a prerequisite for issue #17 (standing up a real cluster).
|
||||||
@@ -6,10 +6,26 @@ Important rule: never create co-authored commits. Never mention Claude in commit
|
|||||||
|
|
||||||
## Project overview
|
## Project overview
|
||||||
|
|
||||||
Cosmic Clash is an open-source, physics-based "vehicle soccer" game (a spiritual successor to Rocket League) built in Godot 4.7, using space ships instead of cars. The project is GDScript/Godot only right now — the "C# backend" mentioned in README.md is planned but not yet started. There is no server-side code; the MVP is local-only play against bots.
|
Cosmic Clash is an open-source, physics-based "vehicle soccer" game (a spiritual successor to Rocket League) built in Godot 4.7, using space ships instead of cars. The game and the dedicated server are GDScript/Godot only — the "C# backend" an early README described was never started, and the dedicated server is an export of this same Godot project. There is one component outside the Godot project: a **Go matchmaking control plane** in `server/` for casual/ranked queues, ranked ratings and Agones-based server allocation. It is a 1.0 launch blocker — see `docs/MATCHMAKING.md` for the design, `multiplayer-next.md` §0 and §7 for what remains (the allocation-to-connect pipeline is now wired end to end; what is left is external — a Steamworks App ID, custom GodotSteam builds, a registry to publish images to, and a live Agones cluster; `TODO.md` orders them), and `docs/TECH_STACK.md` for why the control plane is Go rather than C#, Rust or C++. README.md's "MVP is local-only against bots" section is historical: server-authoritative online multiplayer, a headless dedicated server, Docker/CI verification, and an optional Steam transport are all implemented (Phases 1–6). See `multiplayer-next.md` for what actually remains.
|
||||||
|
|
||||||
Because the gameplay concept (vehicle soccer) can't be copyrighted but specific expression can, all code/art/assets must be original — this is why the project uses Godot instead of Unreal/Unity and ships instead of cars. Keep this in mind when writing code or pulling in assets: don't port or closely mirror Rocket League's actual implementation.
|
Because the gameplay concept (vehicle soccer) can't be copyrighted but specific expression can, all code/art/assets must be original — this is why the project uses Godot instead of Unreal/Unity and ships instead of cars. Keep this in mind when writing code or pulling in assets: don't port or closely mirror Rocket League's actual implementation.
|
||||||
|
|
||||||
|
### Where the documentation lives
|
||||||
|
|
||||||
|
The prose docs carry far more design rationale than the code comments, and several are load-bearing:
|
||||||
|
|
||||||
|
- `multiplayer-next.md` — **the multiplayer task-tracking document**: outstanding work (§0), a numbered "gotchas" list (§9), and the current task breakdown with checkboxes (§7), all in one file. Start at §0 for "what's left". Day-to-day work is Phase 7 (Steam) and Phase 8 (matchmaking), whose numbered task breakdown and acceptance criteria live in §7, because that is the format tasks are picked up from. Phases 0–6 are done and archival — their evidence lives in git history, not the current doc.
|
||||||
|
- `MULTIPLAYER_SPEC.md` — the architecture decisions, wire format, server-side input handling, prediction/reconciliation, latency/frame-rate budget and match lifecycle state machine, as sections 1–6. Code comments across `Game/scripts/` cite it constantly by section number (`§2.4`, `§4.1`); many still say `multiplayer-next.md §N` for `N` 1–6 from before this doc was split out — when a comment does, the content is now here, not there. `multiplayer-next.md`'s own §7+ cites `§N` the same way and disambiguates by number (1–6 → this doc, 7+ → itself).
|
||||||
|
- `TRAINING.md` — the full RL workflow (training, curriculum generations, export, eval, difficulty tiers).
|
||||||
|
- `SERVER.md` — dedicated-server build, config, systemd deploy, sizing.
|
||||||
|
- `STEAM.md` — optional GodotSteam custom-build setup and the transport contract.
|
||||||
|
- `FLIGHT_MANUAL.md` — the player-facing flight model.
|
||||||
|
- `docs/MATCHMAKING.md` — casual/ranked queue design, and the locked constraints (Go/PostgreSQL/Redis/Agones) the `server/` module implements. Partially implemented; a 1.0 launch blocker, and the reason a backend service outside the Godot project exists at all.
|
||||||
|
- `docs/TECH_STACK.md` — what the project is built with and why, including the Go-vs-C#/Rust/C++ rationale for the matchmaking control plane.
|
||||||
|
- `TODO.md` — deferred non-multiplayer work, **and** the ordered human-actionable backlog: which GitHub issue to do first, what each one unblocks, and which items are waiting on nobody. Start there when asking "what next". Audio is no longer absent — a procedural `AudioManager` covers UI, countdown, impact, goal and engine/turbo cues; what remains is authored assets.
|
||||||
|
- `docs/THREAT-MODEL.md`, `docs/SUPPLY-CHAIN.md`, `docs/OBSERVABILITY.md`, `docs/MATCHMAKING-SLOs.md`, `docs/ADR-001-matchmaking-platform.md` — the control plane's security, release, telemetry and SLO contracts. `server/security/*.py` asserts several of them against the checked-in manifests, so changing a manifest often means changing one of these.
|
||||||
|
- `docs/REVIEW-2026-09-feat-multiplayer.md` — a point-in-time adversarial review of this branch. Every finding in it is fixed; it is kept for the reasoning, not as a status report, and its header says so.
|
||||||
|
|
||||||
## Godot MCP server
|
## Godot MCP server
|
||||||
|
|
||||||
This repo vendors [godot-mcp](https://github.com/tugcantopaloglu/godot-mcp) as a git submodule at `mcp/godot-mcp` and registers it in `.mcp.json`. **Prefer the godot-mcp tools over manual file edits or shell commands** when the task involves inspecting or modifying the Godot project — reading/editing scenes, nodes, scripts, running the project, or interacting with a live Godot editor/runtime instance. It understands Godot's scene tree and `.tscn`/`.gd` structures directly, which is more reliable than hand-parsing them.
|
This repo vendors [godot-mcp](https://github.com/tugcantopaloglu/godot-mcp) as a git submodule at `mcp/godot-mcp` and registers it in `.mcp.json`. **Prefer the godot-mcp tools over manual file edits or shell commands** when the task involves inspecting or modifying the Godot project — reading/editing scenes, nodes, scripts, running the project, or interacting with a live Godot editor/runtime instance. It understands Godot's scene tree and `.tscn`/`.gd` structures directly, which is more reliable than hand-parsing them.
|
||||||
@@ -25,36 +41,262 @@ npm run build
|
|||||||
|
|
||||||
`GODOT_PATH` (env var in `.mcp.json`) is left blank to auto-detect the Godot executable; set it explicitly if auto-detection fails on your machine.
|
`GODOT_PATH` (env var in `.mcp.json`) is left blank to auto-detect the Godot executable; set it explicitly if auto-detection fails on your machine.
|
||||||
|
|
||||||
|
## Blender MCP server
|
||||||
|
|
||||||
|
This repo vendors [blender-mcp](https://github.com/ahujasid/blender-mcp) as a git submodule at `mcp/blender-mcp` and registers it in `.mcp.json`. **Prefer the blender-mcp tools over manual scripting** when the task involves creating or editing 3D models, materials, or scenes in Blender (e.g. ship/arena assets) — it drives a live Blender instance directly rather than hand-writing `.blend`/Python scene-manipulation code.
|
||||||
|
|
||||||
|
Setup after cloning (same submodule caveat as godot-mcp above): `git submodule update --init --recursive`.
|
||||||
|
|
||||||
|
**Preflight — run this before the first blender-mcp tool call in a session** (both commands are idempotent and quick when already current; don't repeat them for later calls in the same session):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mcp/blender-mcp
|
||||||
|
uv sync # the submodule pointer moves often and deps drift with it
|
||||||
|
uv run blender-mcp install-addon # copies the bundled addon into Blender's user addons dir
|
||||||
|
```
|
||||||
|
|
||||||
|
`install-addon` replaced the old manual GUI install (Preferences → Add-ons → Install → `addon.py`). It discovers Blender's addons directory itself (`uv run blender-mcp addon-paths` lists candidates; `--addons-dir` or `BLENDERMCP_ADDONS_DIR` overrides), backs up any existing copy, and is version-aware — it compares `ADDON_PROTOCOL_VERSION` in the installed file against the bundled one and rewrites only when the installed copy is missing or older, so a no-op run is cheap. The source of truth is `src/blender_mcp/bundled/addon.py`; the repo-root `addon.py` is an identical copy kept for the legacy manual path.
|
||||||
|
|
||||||
|
What still cannot be scripted, and is the user's job — ask them rather than retrying a failing tool call:
|
||||||
|
|
||||||
|
- Blender must be running with "Interface: Blender MCP" enabled and its socket server started (default `localhost:9876`; override via `BLENDER_HOST` / `BLENDER_PORT` in `.mcp.json`).
|
||||||
|
- If `install-addon` rewrote the addon while Blender was open, the new code isn't loaded until Blender restarts or the addon is disabled/re-enabled. Treat "install-addon reported an update" as a signal to tell the user to restart Blender before continuing.
|
||||||
|
|
||||||
|
Upstream ships telemetry, and there are **two independent switches** — turning off one does not affect the other:
|
||||||
|
|
||||||
|
- *Server side*: disabled here via `DISABLE_TELEMETRY` / `BLENDER_MCP_DISABLE_TELEMETRY` / `MCP_DISABLE_TELEMETRY` in the server's `env` block in `.mcp.json`. This also suppresses the consent prompt that would otherwise arrive through the MCP client on first use. Keep these set when editing `.mcp.json`.
|
||||||
|
- *Addon side*, inside Blender: an "Allow Telemetry" checkbox in the addon's preferences that **defaults to on** and covers prompts, code snippets, screenshots and trajectory data, plus manual-edit capture handlers. The env vars above cannot reach it — it is a Blender preference. Untick it at Preferences → Add-ons → Blender MCP, or clear it with `set_telemetry_consent(false)` via the addon's command channel.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
There is no build step, linter, or automated test suite for the GDScript project itself — Godot projects run directly from source.
|
There is no build step or linter for the GDScript project itself — Godot projects run directly from source.
|
||||||
|
|
||||||
|
### Running the game
|
||||||
|
|
||||||
- **Open the project**: open `Game/` as a project in the Godot 4.7 editor, or run `godot --path Game` from the repo root.
|
- **Open the project**: open `Game/` as a project in the Godot 4.7 editor, or run `godot --path Game` from the repo root.
|
||||||
- **Run the game**: press Play in the editor, or `godot --path Game res://scenes/main_menu.tscn`.
|
- **Run the game**: press Play in the editor, or `godot --path Game res://scenes/main_menu.tscn`.
|
||||||
- **Headless smoke test** (RL/CI precondition — the game must run without rendering): `godot --headless --path Game res://scenes/free_play.tscn`.
|
- **Headless smoke test** (RL/CI precondition — the game must run without rendering): `godot --headless --path Game res://scenes/free_play.tscn`.
|
||||||
|
- **Import resources first** on a fresh checkout or in a container: `godot --headless --path Game --import`. Do **not** add `--quit` — it ends the editor after one iteration and can leave `.godot/imported` half-generated (see the Dockerfile comment).
|
||||||
|
|
||||||
|
### Unit tests
|
||||||
|
|
||||||
|
`godot --headless --path Game res://tests/test_runner.tscn` — pure-function assertions, exits 0/1.
|
||||||
|
|
||||||
|
Add a test by dropping a `*.gd` file under `Game/tests/cases/` that extends `res://tests/test_case.gd` (path-based `extends`, not the bare `class_name` — see that file for why) with any number of `test_*()` methods; the runner discovers it, no registration needed. The same path-vs-`class_name` caveat is why `net_codec.gd`, `sim_constants.gd` etc. are reached by `preload()` elsewhere in the codebase.
|
||||||
|
|
||||||
|
The runner has **no filter flag** — it always runs everything (it's fast). To isolate one case, temporarily move the others out of `tests/cases/`.
|
||||||
|
|
||||||
|
Two runner behaviours exist because of past silent-pass bugs, and new tests must respect them: a case file that fails to parse is a failure (`can_instantiate()` is the guard — `load()` does *not* return null on a broken script), and **a test that completes having made zero assertions is itself a failure**, because GDScript has no exceptions and a crash before the first `assert_*` would otherwise read as a pass.
|
||||||
|
|
||||||
|
### Networking smoke tests (multi-process)
|
||||||
|
|
||||||
|
These need two or three real `godot --headless` processes for a live ENet handshake, so they are **not** part of `test_runner.tscn`. Each prints `SMOKE PASS/FAIL: ...` and exits 0/1. Run them all through the wrapper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make verify-enet-integration # host+client pairs for every case, then the 3-process match
|
||||||
|
VERIFY_ENET_CASES=net,clock make verify-enet-integration # subset, for local debugging
|
||||||
|
GODOT_BIN=/path/to/godot make verify-enet-integration # non-default Godot binary
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/verify_enet_integration.sh` starts each role, waits, and fails on any `SCRIPT ERROR`/`ERROR:`/`SMOKE FAIL` in the logs — a clean exit code alone is not the bar. It prints its temp log directory and dumps the logs on failure. The individual scenes, if you need to drive one by hand with `-- --role=<role>`:
|
||||||
|
|
||||||
|
| Scene | Roles | What it proves |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `tests/net_smoke.tscn` | `host`, `client` | Raw connect/disconnect; the host observes `client_disconnected`, not just clean self-exit. |
|
||||||
|
| `tests/match_net_smoke.tscn` | `host`, `client`, `client-badversion`, `client-longname`, `host_recycle` | Handshake gating (protocol version, oversized name rejection) and that a host→leave→re-host cycle actually empties the roster (run a plain `client` against `host_recycle`). |
|
||||||
|
| `tests/clock_smoke.tscn` | `host`, `client` | Clock convergence, cross-checked against independent OS-wall-clock ground truth rather than self-consistency. |
|
||||||
|
| `tests/lobby_smoke.tscn` | `host`, `client` | **Both** roles load `lobby.tscn` for real via `change_scene_to_file`, exercising the server's read-only view as well as the client's interactive one. |
|
||||||
|
| `tests/networked_match_ci.tscn` | `host`, `client-bot --test-bot` (×2) | A full server + two AI-driven clients playing a real match: snapshot throughput and cross-peer score agreement, via a deterministic server-forced goal. |
|
||||||
|
| `tests/networked_match_smoke.tscn` | see its header | Shorter attended variant of the above. |
|
||||||
|
| `tests/net_sim_smoke.tscn` | see its header | The `--net-sim-*` latency/loss decorator actually changes observed behaviour. |
|
||||||
|
|
||||||
|
See `network_manager.gd`'s header comment and `multiplayer-next.md` §9 gotchas 25–30 for the non-obvious Godot/ENet failure modes these caught (`OfflineMultiplayerPeer` sentinel, premature peer teardown, `change_scene_to_file` off the real `current_scene`, unbounded `connection_failed`, the `is_client`-before-actually-connected race, `load()` not returning null on a broken script).
|
||||||
|
|
||||||
|
**`main_menu.tscn`'s Host/Join flow** is verified the same way but needs a temporary autoload since it's the real main scene, not a wrapper: add `MainMenuTestHooks="*res://tests/main_menu_test_hooks.gd"` to `project.godot [autoload]`, run `godot --headless --path Game res://scenes/main_menu.tscn -- --role=<host|join_ok|join_refused|join_cancel>` (host first, sleep ~1s, then the join role), then remove the autoload line again — it must never ship registered.
|
||||||
|
|
||||||
|
`tests/server_physics_parity.gd` is a standalone `SceneTree` script (no `.tscn`, no wired runner) that traces the real Ship scene through fixed inputs and dumps every physics step's pose/velocity plus the observation vector. It exists to be diffed against the same file run from a `git archive HEAD` tree, proving a client-side change didn't perturb shared physics.
|
||||||
|
|
||||||
|
### Dedicated server (Docker)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make verify-phase6 # the whole exported-server gate; also the entire Phase 6 CI workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds the stripped `Linux Dedicated Server` export, runs it in one container, joins two independent headless clients from two others, forces a server-owned goal in each of two matches, and asserts both clients saw both scores **and that the arena actually rotated between matches**. It needs several GB of free Docker space (the pinned `barichello/godot-ci:4.7.1` image is ~2.4 GB) and always tears down its Compose containers, printing its log directory either way.
|
||||||
|
|
||||||
|
The Dockerfile's targets are worth knowing: `project-imported` (base, resources imported) → `enet-test` (source client, used by the ENet CI workflow) → `exporter` (rewrites `run/main_scene` and exports the server) → `server` (slim Ubuntu runtime) and `smoke-client` (test-only harness). **Godot dedicated exports refuse command-line scene overrides**, which is why the server scene is baked in by `sed` at export time rather than passed as an argument.
|
||||||
|
|
||||||
|
To run one by hand, and for every config flag, see `SERVER.md`. `--smoke-force-goal-after=<seconds>` is a verification-only switch and must never be used for a real match.
|
||||||
|
|
||||||
|
### Steam builds (optional)
|
||||||
|
|
||||||
|
`make verify-steam-templates` requires custom GodotSteam executables pinned in `steam-dependencies.lock.json` and pointed at by `COSMIC_CLASH_STEAM_CLIENT_GODOT` / `COSMIC_CLASH_STEAM_SERVER_GODOT`. It deliberately refuses a stock Godot binary. Nothing else in the repo needs Steam — the default build and every Docker check are ENet-only. See `STEAM.md`.
|
||||||
|
|
||||||
|
### CI
|
||||||
|
|
||||||
|
`.github/workflows/` has eight jobs, all but one running a Make target:
|
||||||
|
|
||||||
|
| Workflow | Runs | Needs |
|
||||||
|
|---|---|---|
|
||||||
|
| `server-unit-tests.yml` | `go build`/`vet`/`vet -tags integration`/`test`/`test -race` in `server/` | nothing (no Docker) |
|
||||||
|
| `dedicated-server-smoke.yml` | `make verify-phase6` | Docker, several GB |
|
||||||
|
| `enet-integration.yml` | `make verify-enet-integration` inside the `enet-test` image | Docker |
|
||||||
|
| `allocated-compose.yml` | `make verify-allocated-compose` | Docker Compose |
|
||||||
|
| `agones-integration.yml` | `make verify-kind-agones` | kind + Helm |
|
||||||
|
| `multiplayer-chaos.yml` | `make verify-chaos-recovery` | Docker |
|
||||||
|
| `multiplayer-load.yml` | `make verify-multiplayer-load` (two `-tags load` Go tests) | — |
|
||||||
|
| `supply-chain.yml` | `make verify-supply-chain` | — |
|
||||||
|
|
||||||
|
**The Godot unit suite runs via `verify-phase6`**, which invokes `test_runner.tscn` as its first step — there is no separate Godot workflow. The Go unit suite has its own workflow because until it existed the only Go tests CI ran were `multiplayer-load`'s two load tests, so ~24k lines of control plane gated nothing.
|
||||||
|
|
||||||
|
Note what is *not* in CI: `make verify-multiplayer-local` (the combined local gate, which also runs the Python manifest/contract suites) and the `integration`-tagged Go tests, which need a real PostgreSQL/Redis and live in `scripts/run_*_integration.sh`. Run those by hand before landing server changes.
|
||||||
|
|
||||||
|
### When a gate fails, suspect the assertion first
|
||||||
|
|
||||||
|
The most expensive failures in this repo have not been broken behaviour. They
|
||||||
|
have been **assertions that cannot distinguish the two states they implicitly
|
||||||
|
claim to**, each reporting its own ambiguity as a confident verdict about the
|
||||||
|
system under test. Five in one session, several costing multiple CI round trips:
|
||||||
|
|
||||||
|
| Assertion | What it actually conflated |
|
||||||
|
|---|---|
|
||||||
|
| `kubectl wait --for=jsonpath='{.status.ready}'` on an Agones Fleet | field does not exist vs. condition unmet — it could never pass |
|
||||||
|
| `compose ps --status running \| grep -qx game-server` | not started *yet* vs. exited |
|
||||||
|
| `remote_residual_position_p99 < 0.3` | real regression vs. host scheduling noise |
|
||||||
|
| a validator reading `status.gameServer` | Agones' real response vs. an invented one, with unit tests asserting the invention |
|
||||||
|
| `docker image inspect` guarding a build | image is current vs. image merely exists, so a rerun verified stale code |
|
||||||
|
|
||||||
|
Before theorising about the code, ask: **can this check tell "broken" from
|
||||||
|
"not ready yet", "absent" from "unset", or "regressed" from "slow"?** If not,
|
||||||
|
that is the bug, whatever else is also true.
|
||||||
|
|
||||||
|
Two habits follow from it, and both repeatedly beat reading code:
|
||||||
|
|
||||||
|
- **Make the script say what it saw before diagnosing why.** Most gates here are
|
||||||
|
`curl -fsS` and bare `[[ ]]` under `set -e`, which abort silently — several CI
|
||||||
|
runs produced nothing but `make: *** Error 1`. Report the failing line and
|
||||||
|
command, print the value that failed its comparison, and dump the surrounding
|
||||||
|
state *before* any cleanup trap destroys it. Every root cause found in that
|
||||||
|
session came from doing this; essentially every confident guess made without
|
||||||
|
it was wrong.
|
||||||
|
- **Verify the diagnostics fire.** Two separate dumps were added and neither ran:
|
||||||
|
one behind a `kubectl cluster-info` guard that misjudged reachability, one
|
||||||
|
because a bare `trap ... ERR` does not fire inside functions or subshells
|
||||||
|
without `set -E`. A diagnostic that has never been seen working is not
|
||||||
|
evidence.
|
||||||
|
|
||||||
|
And when a test and the code agree but reality disagrees, suspect they were
|
||||||
|
written together. A validator and its fixtures both encoded a response shape
|
||||||
|
Agones never sends; nothing caught it because the gate had never run far enough
|
||||||
|
to see a real one.
|
||||||
|
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
- The `mcp/godot-mcp` submodule is a separate Node/TypeScript project with its own `npm install` / `npm run build` (see above) — it is tooling, not part of the game itself.
|
- The `mcp/godot-mcp` submodule is a separate Node/TypeScript project with its own `npm install` / `npm run build` (see above) — it is tooling, not part of the game itself.
|
||||||
|
- `Game/tools/` holds editor-run utilities (`bake_arena_boundary.gd`, `gpu_profile_harness.tscn`, `replay_dump.gd` for reading server replay logs); `tools/blender/` and `tools/textures/` hold the Python generators for the original ship/ball/nebula assets.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
The structure was deliberately chosen so an RL-trained AI opponent and, later, multiplayer bolt on without rework (see `TODO.md` for the deferred work). The three load-bearing seams are the controller abstraction, the arena/game-mode split, and code-driven spawning.
|
The structure was deliberately chosen so an RL-trained AI opponent and, later, multiplayer bolt on without rework. The load-bearing seams are the controller abstraction, the arena/game-mode split, code-driven spawning, and (for networking) the pure-function codec/state modules that can be unit-tested without a live connection.
|
||||||
|
|
||||||
- **Scene flow**: `scenes/main_menu.tscn` (`main_menu.gd`, one handler per mode) → `scenes/free_play.tscn` (practice: no timer, R resets ball) or `scenes/match.tscn` (150s timer, per-team score, kickoff resets). Esc returns to the menu from either mode.
|
### Core game
|
||||||
- **Controller seam (do not bypass)**: `Ship` (`scripts/ship.gd`, `RigidBody3D`) never reads `Input`. Each physics tick, `_integrate_forces` pulls one `ShipAction` (`scripts/ship_action.gd`: thrust `Vector3`, rotation `Vector3`, turbo `bool`, each axis -1..1) from its `ShipController` child (`scripts/ship_controller.gd`, base returns a zero action). `PlayerShipController` reads input actions; a future `AIShipController` (RL policy) or network-replication controller implements the same `get_action()` interface. A ship with no controller is inert but simulated. The ShipAction shape *is* the future RL action space — change it deliberately.
|
|
||||||
- **Arena vs game mode**: `scenes/arena_01.tscn` (`scripts/arena.gd`, group `"arena"`) is a stateless stadium — a setting (space-platform floor, starfield sky, lighting), an enclosing `Boundary` (instance of `objects/arena_boundary.tscn`: floor/walls/ceiling colliders), two `Goal` instances (team 0 and 1), `BallSpawn` and `SpawnsTeam0/1` Marker3Ds — queried via `get_ball_spawn()`/`get_ship_spawns(team)`/`get_goals()`. All arenas are a standard size: they instance the shared `arena_boundary.tscn`, and `scripts/arena_boundary.gd` (`ArenaBoundary`) holds the canonical play-volume constants (inner x ±12, z ±18, height 12, goal lines z ±17) that field-size logic must derive from instead of restating numbers. Game modes extend `GameMode` (`scripts/game_mode.gd`, group `"game"`): the mode's scene contains an Arena + HUD, and the mode spawns ball/ships/controllers/camera **in code** (`spawn_ship(team, index, controller)` etc.) so ship counts and controller mixes stay flexible. `free_play.gd` and `match_mode.gd` override `_start()` and `_on_goal_scored(conceding_team)`.
|
- **Scene flow**: `scenes/main_menu.tscn` (`main_menu.gd`, one handler per mode) → `free_play.tscn` (practice: no timer, R resets ball), `match.tscn` (150s timer, per-team score, kickoff resets), `spectate.tscn` (bot vs bot exhibition), `settings.tscn`, or — for online — `lobby.tscn` → `networked_match.tscn`. Esc returns to the menu. Canonical paths live in `scripts/scene_paths.gd`; use those constants rather than string literals.
|
||||||
|
- **Controller seam (do not bypass)**: `Ship` (`scripts/ship.gd`, `RigidBody3D`) never reads `Input`. Each physics tick, `_integrate_forces` pulls one `ShipAction` (`scripts/ship_action.gd`: thrust `Vector3`, rotation `Vector3`, turbo `bool`, each axis -1..1) from its `ShipController` child (`scripts/ship_controller.gd`, base returns a zero action). `PlayerShipController` reads input actions; `AIShipController` runs an RL policy; `RLShipController` is driven by the training bridge; `LocalNetShipController` wraps another controller to record inputs into the network timeline. A ship with no controller is inert but simulated. The ShipAction shape *is* the RL action space and *is* what the wire format quantises — change it deliberately and everywhere at once.
|
||||||
|
- **Arena vs game mode**: an arena (`scripts/arena.gd`, group `"arena"`) is a stateless stadium — a setting, an enclosing `Boundary` (instance of `objects/arena_boundary.tscn`), two `Goal` instances, `BallSpawn` and `SpawnsTeam0/1` Marker3Ds — queried via `get_ball_spawn()`/`get_ship_spawns(team)`/`get_goals()`. All arenas are a standard size: they instance the shared `arena_boundary.tscn`, and `scripts/arena_boundary.gd` (`ArenaBoundary`) holds the canonical play-volume constants (`INNER_HALF_X` 18, `INNER_HALF_Z` 27, `INNER_HEIGHT` 18, `GOAL_LINE_Z` = `INNER_HALF_Z`) that field-size logic must derive from instead of restating numbers. Game modes extend `GameMode` (`scripts/game_mode.gd`, group `"game"`): the mode's scene contains an Arena + HUD, and the mode spawns ball/ships/controllers/camera **in code** (`spawn_ship(team, index, controller)` etc.) so ship counts and controller mixes stay flexible.
|
||||||
|
- **Arena registry**: `scripts/arena_registry.gd` is the single source of truth for the arena list — three settings × floor/elevated goal variants. `"random": true` gates which arenas Match/Spectate/the dedicated server may pick; **elevated-goal variants are Free-Play-only** until a checkpoint trained on `training_elevated.tscn` is promoted, because the current bots cannot score on an elevated goal. `path_for_match(match_index, mode)` is deliberately pure arithmetic so "the server cycles arenas" is unit-testable. **The Go control plane keeps its own copy of the ranked-eligible subset** (`server/domain/ranked.go`'s `rankedArenas`), because ranked arena selection is a server-authoritative decision made before any Godot process exists. That copy is not free to drift: `server/domain/arena_registry_sync_test.go` parses this file and fails if the two disagree in either direction, or if a ranked path has no scene behind it. Editing the arena list therefore means updating `ranked.go` too — the test says so when it fails. `arena_base.tscn` is the scenery-free physical layout the dedicated server loads (clients still render the variant `MatchSim` names).
|
||||||
- **Goals are dumb sensors**: `scripts/goal.gd` (`Area3D`, group `"goal"`, `@export team`) only emits `goal_scored(team)` when a body in group `"ball"` enters; `GameMode` debounces it (`_handle_goal_scored`) and modes decide consequences. Never put scoring/reset logic in the goal.
|
- **Goals are dumb sensors**: `scripts/goal.gd` (`Area3D`, group `"goal"`, `@export team`) only emits `goal_scored(team)` when a body in group `"ball"` enters; `GameMode` debounces it (`_handle_goal_scored`) and modes decide consequences. Never put scoring/reset logic in the goal.
|
||||||
- **Ship physics**: all movement is force/torque-based (`_integrate_forces`), not kinematic — inputs become world-space forces/torques relative to ship orientation, with manual drag and speed clamps per tick. Physics formulas are commented inline; see `FLIGHT_MANUAL.md` for the player-facing flight model. Physics properties (mass, inertia, friction material) live in `objects/ship.tscn`, not in `_ready` overrides — keep the scene truthful; RL tuning depends on it.
|
- **Ship physics**: all movement is force/torque-based (`_integrate_forces`), not kinematic — inputs become world-space forces/torques relative to ship orientation, with manual drag and speed clamps per tick. Physics properties (mass, inertia, friction material) live in `objects/ship.tscn`, not in `_ready` overrides — keep the scene truthful; RL tuning and the client/server parity trace depend on it.
|
||||||
- **Surface pull (wall/ceiling grav-plating)**: `ArenaBoundary.get_surface_pull()` is a wall+ceiling-only proximity force field (the floor stays plain default gravity) that `Ship` and `Ball` (`scripts/ball.gd`) each apply in their own `_integrate_forces` with independently-tuned strength/range, discovered via the `"arena_boundary"` group — enabling wall-rides and ceiling shots with no collision-shape changes. Because it runs inside `Ship`'s shared `_integrate_forces`, it reaches trained bots too; see `TRAINING.md` for the retrain this warrants.
|
- **Surface pull (wall/ceiling grav-plating)**: `ArenaBoundary.get_surface_pull()` is a wall+ceiling-only proximity force field (the floor stays plain default gravity) that `Ship` and `Ball` (`scripts/ball.gd`) each apply in their own `_integrate_forces` with independently-tuned strength/range, discovered via the `"arena_boundary"` group — enabling wall-rides and ceiling shots with no collision-shape changes.
|
||||||
- **Camera** (`scenes/ship_camera_rig.tscn`, `scripts/ship_camera.gd`, group `"ship_camera"`) is spawned by the game mode and given a `target` ship — ships have no camera/HUD dependency, so headless RL runs work (`godot --headless`).
|
- **Camera** (`scenes/ship_camera_rig.tscn`, `scripts/ship_camera.gd`, group `"ship_camera"`) is spawned by the game mode and given a `target` ship — ships have no camera/HUD dependency, so headless runs work.
|
||||||
- **HUD / telemetry pattern**: `Ship` emits flight data via signals only when values change past thresholds (`_last_*` fields, `*_THRESHOLD` constants). `HUDController` (`scripts/HUDController.gd` on `scenes/HUD.tscn`, instanced by each mode's scene) discovers the ship, camera rig, and game mode via groups (`"ship"`, `"ship_camera"`, `"game"`), connects to signals, and only updates labels — no polling. Follow this discovery-by-group + signal-push pattern for new instruments or cross-node communication, not hardcoded `get_node` paths or per-frame polling.
|
- **HUD / telemetry pattern**: `Ship` emits flight data via signals only when values change past thresholds (`_last_*` fields, `*_THRESHOLD` constants). `HUDController` (`scripts/HUDController.gd`) discovers the camera rig and game mode via groups (`"ship_camera"`, `"game"`) but receives its target ship directly from the game mode via `GameMode.spawn_camera_rig`, connects to signals, and only updates labels — no polling. Follow this discovery-by-group + signal-push pattern for new instruments, not hardcoded `get_node` paths or per-frame polling. **`HUDController` duck-types on optional signals** (`timer_updated`, `match_ended`, `kickoff_countdown`, …) and hides the corresponding widget when a mode lacks one — so declaring a signal you never emit is worse than not declaring it (it shows a permanently frozen timer instead of hiding it).
|
||||||
- **Input actions** are defined in `Game/project.godot` under `[input]` (`move_forward`, `turn_left`, `turbo`, `reset_ball`, etc.) and read only by `PlayerShipController` (plus mode-level `_unhandled_input` for `reset_ball`/`ui_cancel`) — add new controls there rather than hardcoding key checks.
|
- **`SimConstants.TICK_HZ`** is the single source of truth for the 60 Hz tick — every derived timing constant reads it rather than restating `60`. It is *not* wired to `project.godot`'s `physics/common/physics_ticks_per_second` (an engine setting), so those must be kept in sync by hand.
|
||||||
- Physics engine is Jolt (`Game/project.godot`, `[physics] 3d/physics_engine="Jolt Physics"`).
|
- **Input actions** are defined in `Game/project.godot` under `[input]` (`move_forward`, `turbo`, `reset_ball`, `toggle_perf_overlay` F3, `toggle_net_overlay` F4, …) and read only by `PlayerShipController` plus mode-level `_unhandled_input` — add new controls there rather than hardcoding key checks.
|
||||||
|
- Physics engine is Jolt (`[physics] 3d/physics_engine="Jolt Physics"`).
|
||||||
|
|
||||||
|
### `project.godot` hazard
|
||||||
|
|
||||||
|
Godot's `ConfigFile` writer does not round-trip comments, and a `#` block directly above a setting can be spliced into that setting's own line on rewrite, silently commenting it out. **Do not add comments to `Game/project.godot`.** This matters most for the feature overrides `run/main_scene.training` and `run/main_scene.dedicated_server`, which are how the training and server exports reach the right scene without a CLI flag; `tests/cases/test_project_settings.gd` fails loudly if they ever break.
|
||||||
|
|
||||||
|
### Networking (server-authoritative, with client prediction)
|
||||||
|
|
||||||
|
All hot-path RPCs live on **autoloads**, never scene nodes, so RPC NodePaths never depend on which scene is loaded. The autoload chain, in `project.godot` order:
|
||||||
|
|
||||||
|
- **`NetSim`** (`net_sim.gd`) — debug-only seeded latency/jitter/loss/duplicate decorator around outgoing dispatch. A pure passthrough unless `--net-sim-latency=` / `--net-sim-jitter=` / `--net-sim-loss=` / `--net-sim-dup=` / `--net-sim-seed=` are passed, so its mere existence changes nothing. Each process reads only its own flags, which is what makes asymmetric (e.g. lossy-upload-only) testing free.
|
||||||
|
- **`NetworkManager`** (`network_manager.gd`) — transport-neutral host/join/shutdown and connection signals. Sets `server_relay = false` the moment a peer exists (the default `true` would let any client RPC any other client through the server). Runs **manual multiplayer polling** (`set_multiplayer_poll_enabled(false)`), because SceneTree's automatic poll runs on the idle frame and would cost a frame in each direction for RPCs issued from `_physics_process` — anything driving a connection must call `NetworkManager.poll()` itself or nothing is ever sent or received.
|
||||||
|
- **`MatchNet`** (`match_net.gd`) — hello/welcome handshake, strict `protocol_version` and tick-rate gating, and the roster (name, team, ready) that survives the lobby→match transition. Slot assignment is *not* stored here; it's derived at spawn time.
|
||||||
|
- **`MatchSim`** (`match_sim.gd`) — the Phase 2+ simulation RPCs: `match_config`, `input` (client→server), `snapshot` (server→client), score/state/kickoff/goal/clock messages. Also owns protocol-level input validation (leaky-bucket packet and byte rate limits), because framing abuse is independent of any particular match's state. Emits `input_rejected` with the verbatim bytes so the replay log can explain "my input did nothing".
|
||||||
|
- **`NetDebugOverlay`** (F4) and **`PerfOverlay`** (F3) — headless-guarded read-only overlays.
|
||||||
|
|
||||||
|
Supporting modules, deliberately standalone (`RefCounted`/`class_name`, no scene or RPC dependency) so they unit-test head-on:
|
||||||
|
|
||||||
|
- `net_codec.gd` — **the wire format**. `PROTOCOL_VERSION`, channel intents, quantisers, pack/unpack for the input and snapshot packets. Any change here is a protocol change.
|
||||||
|
- `match_state.gd` — the match lifecycle enum and its legal-transition table. **The integer values are the wire format** (`match_state` is a u8 in the snapshot header): never renumber an existing state, only append.
|
||||||
|
- `net_body_state.gd`, `net_interpolator.gd`, `net_ship_predictor.gd`, `local_prediction_history.gd`, `local_input_timeline.gd`, `input_jitter_buffer.gd`, `input_lead_controller.gd`, `adaptive_input_depth_controller.gd`, `ship_action_codec.gd`, `replay_log.gd`, `server_config.gd`, `server_log.gd` — each has a header comment explaining its role and the task it came from.
|
||||||
|
|
||||||
|
`networked_match.gd` (`NetworkedMatch extends GameMode`, ~2.4k lines) is where it all meets: the server simulates every slot and broadcasts 60 Hz snapshots; a client simulates **only its own unfrozen slot** with one real controller while every remote slot and the ball stay frozen and interpolated. Its scene has no Arena or HUD child — both are built in code once the arena is actually known (the server picks it, the client learns it from `match_config`), which is why it overrides `_ready()` entirely rather than using `GameMode`'s arena-required-synchronously flow.
|
||||||
|
|
||||||
|
Server process: `scenes/server_boot.tscn` (`server_boot.gd`) is the shell — strict CLI parsing via `ServerConfig` (unknown flag or bad value refuses to start), structured logging via `ServerLog`, physics-overrun watchdog. `ServerMatchLoop` (`server_match_loop.gd`) is the actual match loop: wait for `--min-players` **by roster, not raw peers**, count down, load the next arena from the rotation, run the match, return to the lobby, repeat or drain at `--max-matches`. It parents itself to the scene-tree **root**, never `current_scene`, because `change_scene_to_file` frees the live scene and an orchestrator freed by its own transition can't orchestrate the next one.
|
||||||
|
|
||||||
|
Known-insecure, and the reason public hosting is gated: **slot reclaim is keyed by display name**, so anyone who knows a disconnected player's name can take their reserved slot. Verified Steam identity (Phase 7) is the fix. Don't expose a server to strangers before then.
|
||||||
|
|
||||||
|
### Matchmaking control plane (`server/`, Go)
|
||||||
|
|
||||||
|
The only component outside the Godot project, and roughly a third of the
|
||||||
|
codebase. Layered so policy is testable without a database and persistence
|
||||||
|
without a network:
|
||||||
|
|
||||||
|
- `domain/` (~3.2k lines) — **pure policy, no I/O**: matcher formation and
|
||||||
|
rating tolerance, Glicko ratings and tiers, proposal/queue/match state
|
||||||
|
machines, casual lineup and backfill selection, probe validation, join
|
||||||
|
authorisations. Most behaviour worth asserting lives here and needs no
|
||||||
|
fixture. `ranked.go`'s arena list is checked against `arena_registry.gd` (see
|
||||||
|
Arena registry above).
|
||||||
|
- `store/` (~5.3k) — PostgreSQL boundaries. Every mutation goes through
|
||||||
|
`RunSerializable`; contention is expected rather than exceptional, so the
|
||||||
|
retry budget and jittered backoff there are load-bearing, not decoration.
|
||||||
|
- `api/` (~2.5k) — HTTP surface and the outbox dispatchers. `Service` is a
|
||||||
|
struct of optional providers, each nil-guarded into a 503, which is why a
|
||||||
|
binary can look healthy while a whole feature is unreachable — check what
|
||||||
|
`cmd/*/main.go` actually assigns before concluding a feature is broken.
|
||||||
|
- `allocator/`, `supervisor/`, `agones/` — allocation, the Go process that
|
||||||
|
wraps the exported Godot server in an Agones pod, and the Agones client.
|
||||||
|
- `matcher/`, `workload/`, `observability/`, `steam/`, `testkit/` — the matcher
|
||||||
|
worker loop, workload-token signing, metrics, the Steam Web API adapter, and
|
||||||
|
deterministic offline fakes.
|
||||||
|
|
||||||
|
`cmd/` holds seven binaries: `control-plane`, `matcher`, `allocator`,
|
||||||
|
`maintenance`, `game-server-supervisor`, `migrate`, and `testkit-api`.
|
||||||
|
**`testkit-api` is test-only** — it injects a fake Steam login that accepts any
|
||||||
|
ticket, and must never be deployed in place of `control-plane`.
|
||||||
|
|
||||||
|
Three things that are easy to get wrong:
|
||||||
|
|
||||||
|
- **Integration tests are behind `//go:build integration`** and need a real
|
||||||
|
PostgreSQL/Redis, so `go test ./...` silently skips them. Run them through
|
||||||
|
`scripts/run_*_integration.sh`, which start their own disposable containers.
|
||||||
|
`go vet -tags integration ./...` is worth running too, or those files rot
|
||||||
|
uncompiled.
|
||||||
|
- **Config is start-time.** Tier bands, the join-signing key set, Steam
|
||||||
|
credentials and the probe providers are all read once in `main()`. Changing
|
||||||
|
them is a rolling restart, not a hot reload — deliberate, and consistent with
|
||||||
|
how everything else in these binaries is supplied.
|
||||||
|
- **The wire contract is versioned.** `contracts/v1/openapi.json` and
|
||||||
|
`state-transitions.json` are asserted by `contracts/v1/test_contracts.py`;
|
||||||
|
changing a status code or operation ID without updating them breaks generated
|
||||||
|
clients silently.
|
||||||
|
|
||||||
|
### Steam transport
|
||||||
|
|
||||||
|
`net_transport.gd` (`NetTransport`) is a deliberately narrow boundary: a transport only *creates a peer*; `NetworkManager` keeps ownership of polling, RPC policy and lifecycle. `enet_transport.gd` and `steam_transport.gd` implement it. `NetworkManager.host()/join()` default to `"enet"`; passing `"steam"` **never falls back** — a missing custom build or failed init returns an error naming the missing prerequisite (`steam_bootstrap.gd` produces those messages). Discovery and server advertisement are intentionally unimplemented until a project-owned App ID exists; the local default is Valve's Spacewar App ID 480, which must never be used to advertise servers or ship.
|
||||||
|
|
||||||
## Reinforcement learning / AI bots
|
## Reinforcement learning / AI bots
|
||||||
|
|
||||||
See `TRAINING.md` for the full workflow (training, exporting, evaluating, difficulty tiers). Architecture summary:
|
See `TRAINING.md` for the full workflow. Architecture summary:
|
||||||
|
|
||||||
- `scenes/training.tscn` (`scripts/training_mode.gd`, extends `GameMode`) is the headless self-play environment: two ships driven by `RLShipController`s, with `ShipAIController` (extends the vendored plugin's `AIController3D`) as the only class touching godot_rl types. The plugin is vendored (not a submodule) at `Game/addons/godot_rl_agents` — see its `VENDORED.md`; its C#/ONNX files are unused.
|
- `scenes/training.tscn` / `training_elevated.tscn` (`scripts/training_mode.gd`, extends `GameMode`) is the headless self-play environment: two ships driven by `RLShipController`s, with `ShipAIController` (extends the vendored plugin's `AIController3D`) as the only class touching godot_rl types. The plugin is vendored (not a submodule) at `Game/addons/godot_rl_agents` — see its `VENDORED.md`; its C#/ONNX files are unused.
|
||||||
- `scripts/ship_observations.gd` is the shared observation builder used by both training and in-game inference — never fork or diverge these two paths. Team 1's observations are mirrored (180° about Y) so one policy plays both sides.
|
- `scripts/ship_observations.gd` is the shared observation builder used by training, in-game inference, **and** the server — never fork or diverge these paths. Team 1's observations are mirrored (180° about Y) so one policy plays both sides.
|
||||||
- In-game bots: `scripts/ai_ship_controller.gd` (a `ShipController`) runs the exported policy JSON via `scripts/policy_network.gd` (pure-GDScript MLP) — no .NET/ONNX/Python at runtime. Models live in `Game/bots/`; Match mode's `bot_model_path`/`bot_reaction_ticks`/`bot_action_noise` exports configure the opponent.
|
- In-game bots: `scripts/ai_ship_controller.gd` (a `ShipController`) runs the exported policy JSON via `scripts/policy_network.gd` (pure-GDScript MLP) — no .NET/ONNX/Python at runtime. Models live in `Game/bots/` (promoted tiers in `Game/bots/promoted/`); Match mode's `bot_model_path`/`bot_reaction_ticks`/`bot_action_noise` exports configure the opponent, with the main menu's `GameSettings` autoload selections overriding them.
|
||||||
- Python side lives in `training/` (venv, not committed): `train.py` (SB3 PPO, launches parallel headless Godot instances from source), `export_policy.py` (checkpoint → JSON with parity check), `evaluate.py` (head-to-head eval, appends `training/eval_history.json`).
|
- Python side lives in `training/` (venv, not committed): `train.py` (SB3 PPO, launches parallel headless Godot instances from source), `export_policy.py` (checkpoint → JSON with parity check), `evaluate.py` (head-to-head eval, appends `training/eval_history.json`), plus the curriculum drivers (`curriculum.py`, `generation5.py`) and their JSON state files.
|
||||||
- The flattened action space is Box(7) in gymnasium's **sorted-key order**: rotation xyz, thrust xyz, turbo (>0 = on). The fields are `ShipAction`'s, but gymnasium alphabetizes Dict spaces, so the flat order is NOT ShipAction's thrust-first declaration order — `AIShipController._decide` consumes exported policies in sorted order; change the action space only deliberately and everywhere together.
|
- The flattened action space is Box(7) in gymnasium's **sorted-key order**: rotation xyz, thrust xyz, turbo (>0 = on). The fields are `ShipAction`'s, but gymnasium alphabetizes Dict spaces, so the flat order is NOT ShipAction's thrust-first declaration order — `AIShipController._decide` consumes exported policies in sorted order; change the action space only deliberately and everywhere together.
|
||||||
|
|||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
# Local-only dedicated-server build and verification image. Pin the Godot
|
||||||
|
# release family used by project.godot; no image is pushed by this repository.
|
||||||
|
# barichello/godot-ci:4.7.1 (linux/amd64), resolved 2026-08-29.
|
||||||
|
FROM --platform=linux/amd64 barichello/godot-ci@sha256:622e5ca81b54cd8038ecf7de5d157b47efc800d7cf635af2eec18a6aee4bab7e AS project-imported
|
||||||
|
WORKDIR /workspace
|
||||||
|
# The pinned headless Godot image already runs imports without fontconfig.
|
||||||
|
# Do not refresh its old Ubuntu archive here: its historical keyring rejects
|
||||||
|
# current Noble signatures, while this source-only import stage needs no OS
|
||||||
|
# packages at all.
|
||||||
|
COPY Game /workspace/Game
|
||||||
|
# `--import` starts the editor, waits for resource import to finish, then
|
||||||
|
# exits. Do not combine it with `--quit`, which ends the editor after one
|
||||||
|
# iteration and can interrupt generation of `.godot/imported` resources.
|
||||||
|
RUN godot --headless --path Game --import \
|
||||||
|
&& test -f Game/.godot/imported/nebula_station.glb-fa9a6dd87ae3789d04205b52215e2e76.scn \
|
||||||
|
&& test -f Game/.godot/imported/nebula_debris.glb-39af77a17c0998b5b73172577d906cb9.scn \
|
||||||
|
&& test -f Game/.godot/imported/nebula_planet.glb-2431590907ff85cf1057e7d6ad614ed7.scn
|
||||||
|
|
||||||
|
# Test the source client from an untouched, fully imported project. The
|
||||||
|
# dedicated-server export below rewrites the main scene and must not be used
|
||||||
|
# to run client integration tests.
|
||||||
|
FROM project-imported AS enet-test
|
||||||
|
COPY scripts/verify_enet_integration.sh /workspace/scripts/verify_enet_integration.sh
|
||||||
|
|
||||||
|
# Godot dedicated exports disallow command-line scene overrides. Bake the
|
||||||
|
# server scene into this export (the interactive project's source stays
|
||||||
|
# unchanged), then generate the global-script/autoload metadata it needs.
|
||||||
|
FROM project-imported AS exporter
|
||||||
|
RUN sed -i 's|^run/main_scene=.*$|run/main_scene="res://scenes/server_boot.tscn"|' Game/project.godot \
|
||||||
|
&& mkdir -p /opt/cosmic-clash \
|
||||||
|
&& godot --headless --path Game --export-release "Linux Dedicated Server" /opt/cosmic-clash/CosmicClashServer.x86_64
|
||||||
|
|
||||||
|
# ubuntu:noble linux/amd64 manifest, resolved 2026-09-03. The prior pin
|
||||||
|
# carried an obsolete archive keyring and rejected current Noble signatures
|
||||||
|
# during apt-get update. This remains a digest pin; package verification is
|
||||||
|
# deliberately not bypassed.
|
||||||
|
FROM --platform=linux/amd64 ubuntu@sha256:1e0a86e57d247923571b75e0aaf48a1449cf8c543d51fb3e07a4a7d7bfa79316 AS server
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends libfontconfig1 libgl1 libstdc++6 && rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY --from=exporter /opt/cosmic-clash/ /opt/cosmic-clash/
|
||||||
|
COPY deploy/cosmic-clash-server /opt/cosmic-clash/cosmic-clash-server
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/cosmic-clash-server
|
||||||
|
WORKDIR /opt/cosmic-clash
|
||||||
|
EXPOSE 7777/udp
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/cosmic-clash-server"]
|
||||||
|
|
||||||
|
# Test-only target: runs the source client harness against the exported server.
|
||||||
|
FROM exporter AS smoke-client
|
||||||
|
WORKDIR /workspace
|
||||||
|
ENTRYPOINT ["godot", "--headless", "--path", "Game", "res://tests/export_server_smoke.tscn", "--"]
|
||||||
|
|
||||||
|
# Builds the process supervisor (server/supervisor, multiplayer-next.md task
|
||||||
|
# 8.27/8.28) that wraps the Agones-allocated dedicated server as PID 1.
|
||||||
|
# golang:1.23-alpine (matches server/go.mod's `go 1.23`), resolved 2026-09-01.
|
||||||
|
FROM --platform=linux/amd64 golang@sha256:383395b794dffa5b53012a212365d40c8e37109a626ca30d6151c8348d380b5f AS supervisor-build
|
||||||
|
WORKDIR /workspace/server
|
||||||
|
COPY server/go.mod server/go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY server/ ./
|
||||||
|
RUN CGO_ENABLED=0 go build -o /opt/cosmic-clash/game-server-supervisor ./cmd/game-server-supervisor
|
||||||
|
RUN CGO_ENABLED=0 go build -o /opt/cosmic-clash/control-plane ./cmd/control-plane
|
||||||
|
RUN CGO_ENABLED=0 go build -o /opt/cosmic-clash/testkit-api ./cmd/testkit-api
|
||||||
|
RUN CGO_ENABLED=0 go build -o /opt/cosmic-clash/matcher ./cmd/matcher
|
||||||
|
RUN CGO_ENABLED=0 go build -o /opt/cosmic-clash/allocator ./cmd/allocator
|
||||||
|
RUN CGO_ENABLED=0 go build -o /opt/cosmic-clash/maintenance ./cmd/maintenance
|
||||||
|
|
||||||
|
# Agones-allocated fleet image: the same dedicated-server export as `server`
|
||||||
|
# (unchanged above; make verify-phase6 exercises that target exactly as
|
||||||
|
# before), wrapped by the Go supervisor as PID 1 instead of the direct
|
||||||
|
# launcher script -- required for process-ready/assignment-ready Agones SDK
|
||||||
|
# calls and control-plane registration (multiplayer-next.md §8.27/§8.28).
|
||||||
|
# deploy/k8s/base/fleet.yaml invokes this target with the deployment-specific
|
||||||
|
# supervisor flags and mounts the roster/signing material required by the
|
||||||
|
# allocated startup path. Workload credentials are delivered through the
|
||||||
|
# Agones allocation annotation; a projected token volume is not required.
|
||||||
|
FROM server AS game-server
|
||||||
|
COPY --from=supervisor-build /opt/cosmic-clash/game-server-supervisor /opt/cosmic-clash/game-server-supervisor
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/game-server-supervisor
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/game-server-supervisor"]
|
||||||
|
|
||||||
|
# The production control-plane API. deploy/k8s/base/control-plane-deployment.yaml
|
||||||
|
# has always referenced this image, but nothing built it: cmd/control-plane was
|
||||||
|
# absent from the Go build stage and no target existed, so the checked-in
|
||||||
|
# Kubernetes base could not produce its own advertised topology.
|
||||||
|
#
|
||||||
|
# This must never be substituted with the testkit-api target below, which
|
||||||
|
# injects a fake login provider that accepts any ticket string.
|
||||||
|
FROM server AS control-plane
|
||||||
|
COPY --from=supervisor-build /opt/cosmic-clash/control-plane /opt/cosmic-clash/control-plane
|
||||||
|
COPY server/migrations /opt/cosmic-clash/migrations
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/control-plane
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/control-plane"]
|
||||||
|
|
||||||
|
# TEST ONLY. Supplies a fake Steam login that accepts any ticket; never deploy
|
||||||
|
# this in place of the control-plane target above.
|
||||||
|
FROM server AS testkit-api
|
||||||
|
COPY --from=supervisor-build /opt/cosmic-clash/testkit-api /opt/cosmic-clash/testkit-api
|
||||||
|
COPY server/migrations /opt/cosmic-clash/migrations
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/testkit-api
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/testkit-api"]
|
||||||
|
|
||||||
|
FROM server AS matcher
|
||||||
|
COPY --from=supervisor-build /opt/cosmic-clash/matcher /opt/cosmic-clash/matcher
|
||||||
|
COPY server/migrations /opt/cosmic-clash/migrations
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/matcher
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/matcher"]
|
||||||
|
|
||||||
|
FROM server AS allocator
|
||||||
|
COPY --from=supervisor-build /opt/cosmic-clash/allocator /opt/cosmic-clash/allocator
|
||||||
|
COPY server/migrations /opt/cosmic-clash/migrations
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/allocator
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/allocator"]
|
||||||
|
|
||||||
|
FROM server AS maintenance
|
||||||
|
COPY --from=supervisor-build /opt/cosmic-clash/maintenance /opt/cosmic-clash/maintenance
|
||||||
|
COPY server/migrations /opt/cosmic-clash/migrations
|
||||||
|
RUN chmod 0755 /opt/cosmic-clash/maintenance
|
||||||
|
ENTRYPOINT ["/opt/cosmic-clash/maintenance"]
|
||||||
+52
-12
@@ -27,18 +27,58 @@ Welcome to space, pilot! This guide will teach you everything you need to know a
|
|||||||
|
|
||||||
### Controller Controls
|
### Controller Controls
|
||||||
|
|
||||||
#### Translation
|
Button names are the Xbox layout; a PlayStation pad maps the same physical
|
||||||
|
positions (A = ✕, B = ○, X = □, Y = △).
|
||||||
|
|
||||||
- Left Stick - Strafe (Left/Right) + Thrust (Forward/Back)
|
**The left stick points the nose, the right stick moves the hull.** Your ship has
|
||||||
- Right Trigger - Forward Thrust
|
six degrees of freedom and a pad has exactly six analog axes, so every one gets a
|
||||||
- Left Trigger - Reverse Thrust
|
real axis rather than an on/off button.
|
||||||
- Face Buttons - Up/Down Thrust
|
|
||||||
|
|
||||||
#### Rotation
|
#### Rotation — left stick and shoulders
|
||||||
|
|
||||||
- Right Stick - Pitch/Yaw
|
- Left Stick (left/right) - Yaw
|
||||||
- Shoulder Buttons - Roll
|
- Left Stick (up/down) - Pitch. Flight-sim polarity by default: **push the
|
||||||
- `A Button` - Turbo Boost
|
stick forward and the nose goes down.** Flip it with "Invert pitch" in
|
||||||
|
Settings → Controls.
|
||||||
|
- `LB` - Roll Left (Bank Left)
|
||||||
|
- `RB` - Roll Right (Bank Right)
|
||||||
|
|
||||||
|
#### Translation — right stick and triggers
|
||||||
|
|
||||||
|
- `RT` - Forward Thrust (Main Engines)
|
||||||
|
- `LT` - Reverse Thrust (Retro Engines)
|
||||||
|
- Right Stick (left/right) - Strafe (Port/Starboard Thrusters)
|
||||||
|
- Right Stick (up/down) - Thrust Up/Down (Dorsal/Ventral Thrusters)
|
||||||
|
- `L3` (click the left stick) - Turbo Boost
|
||||||
|
|
||||||
|
`X` and `Y` are deliberately unused in flight, and `A`/`B` are menu-only, so a
|
||||||
|
reflexive face-button press never does anything mid-match.
|
||||||
|
|
||||||
|
#### Menus
|
||||||
|
|
||||||
|
- D-Pad or Left Stick - move the highlight
|
||||||
|
- `A` - select
|
||||||
|
- `B` - back
|
||||||
|
- `Start` - leave a match in progress (deliberately not `B`, which is too easy
|
||||||
|
to press by accident mid-game)
|
||||||
|
|
||||||
|
#### Other
|
||||||
|
|
||||||
|
- `R3` (click the right stick) - Toggle ball camera
|
||||||
|
- `D-Pad Up` - Reset the ball (Free Play only)
|
||||||
|
|
||||||
|
The triggers and sticks are **analog**: a half-pulled trigger gives half thrust,
|
||||||
|
and a gentle stick lean gives a gentle turn. Keyboard keys are all-or-nothing,
|
||||||
|
which is the main reason a pad is easier to fly precisely.
|
||||||
|
|
||||||
|
### Rebinding
|
||||||
|
|
||||||
|
Every control above — keyboard and controller alike — can be remapped in
|
||||||
|
**Settings → Controls**. Pick the device with the Keyboard/Controller toggle,
|
||||||
|
click the binding you want to change, and press the key or button to assign.
|
||||||
|
Binding an input that is already in use unbinds it from the action that had it,
|
||||||
|
and the screen tells you which. "Reset all bindings to defaults" restores this
|
||||||
|
table.
|
||||||
|
|
||||||
## 🛸 Basic Flight Principles
|
## 🛸 Basic Flight Principles
|
||||||
|
|
||||||
@@ -69,7 +109,7 @@ Your ship is a **realistic space vehicle** with the following characteristics:
|
|||||||
|
|
||||||
#### Camera Control
|
#### Camera Control
|
||||||
|
|
||||||
- **Ball Cam**: Press `Enter` to toggle ball tracking camera
|
- **Ball Cam**: Press `Space` (or `R3` on a controller) to toggle ball tracking camera
|
||||||
- **Ship Cam**: Normal follow camera that looks where your ship points
|
- **Ship Cam**: Normal follow camera that looks where your ship points
|
||||||
|
|
||||||
### Intermediate Maneuvers
|
### Intermediate Maneuvers
|
||||||
@@ -115,7 +155,7 @@ Your ship is a **realistic space vehicle** with the following characteristics:
|
|||||||
|
|
||||||
## 🎮 Ball Cam vs Ship Cam
|
## 🎮 Ball Cam vs Ship Cam
|
||||||
|
|
||||||
### Ball Cam Mode (`Enter` to toggle)
|
### Ball Cam Mode (`Space` / `R3` to toggle)
|
||||||
|
|
||||||
- **Camera**: Always looks toward the ball
|
- **Camera**: Always looks toward the ball
|
||||||
- **Ship Control**: Based on ship orientation (NOT camera view)
|
- **Ship Control**: Based on ship orientation (NOT camera view)
|
||||||
@@ -133,7 +173,7 @@ Your ship is a **realistic space vehicle** with the following characteristics:
|
|||||||
|
|
||||||
### Turbo System
|
### Turbo System
|
||||||
|
|
||||||
- **Activation**: Hold `Shift` (keyboard) or `A` (controller) while thrusting forward
|
- **Activation**: Hold `Shift` (keyboard) or `L3` (controller) while thrusting forward
|
||||||
- **Effect**: 2.5x thrust multiplier on main engines only
|
- **Effect**: 2.5x thrust multiplier on main engines only
|
||||||
- **Strategy**: Use for quick acceleration or emergency maneuvers
|
- **Strategy**: Use for quick acceleration or emergency maneuvers
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Blender authoring sources live here, but the game consumes the exported
|
||||||
|
# runtime assets under res://assets/models. Keep Godot's project scanner from
|
||||||
|
# requiring Blender when importing or testing in headless environments.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,63 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="scene"
|
|
||||||
importer_version=1
|
|
||||||
type="PackedScene"
|
|
||||||
uid="uid://85qbs3rignos"
|
|
||||||
path="res://.godot/imported/gold_ball.blend-c2b8b4e1fb4c47cd0c20f77e5daf34c2.scn"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/blender_models/gold_ball.blend"
|
|
||||||
dest_files=["res://.godot/imported/gold_ball.blend-c2b8b4e1fb4c47cd0c20f77e5daf34c2.scn"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
nodes/root_type=""
|
|
||||||
nodes/root_name=""
|
|
||||||
nodes/root_script=null
|
|
||||||
mesh_library/use_node_names_as_mesh_names=false
|
|
||||||
array_mesh/deduplicate_surfaces=true
|
|
||||||
nodes/apply_root_scale=true
|
|
||||||
nodes/root_scale=1.0
|
|
||||||
nodes/import_as_skeleton_bones=false
|
|
||||||
nodes/use_name_suffixes=true
|
|
||||||
nodes/use_node_type_suffixes=true
|
|
||||||
meshes/ensure_tangents=true
|
|
||||||
meshes/generate_lods=true
|
|
||||||
meshes/create_shadow_meshes=true
|
|
||||||
meshes/light_baking=1
|
|
||||||
meshes/lightmap_texel_size=0.2
|
|
||||||
meshes/force_disable_compression=false
|
|
||||||
skins/use_named_skins=true
|
|
||||||
animation/import=true
|
|
||||||
animation/fps=30
|
|
||||||
animation/trimming=false
|
|
||||||
animation/remove_immutable_tracks=true
|
|
||||||
animation/import_rest_as_RESET=false
|
|
||||||
import_script/path=""
|
|
||||||
materials/extract=0
|
|
||||||
materials/extract_format=0
|
|
||||||
materials/extract_path=""
|
|
||||||
_subresources={}
|
|
||||||
blender/nodes/visible=0
|
|
||||||
blender/nodes/active_collection_only=false
|
|
||||||
blender/nodes/punctual_lights=true
|
|
||||||
blender/nodes/cameras=true
|
|
||||||
blender/nodes/custom_properties=true
|
|
||||||
blender/nodes/modifiers=1
|
|
||||||
blender/meshes/vertex_colors=2
|
|
||||||
blender/meshes/uvs=true
|
|
||||||
blender/meshes/normals=true
|
|
||||||
blender/meshes/export_geometry_nodes_instances=false
|
|
||||||
blender/meshes/gpu_instances=false
|
|
||||||
blender/meshes/tangents=true
|
|
||||||
blender/meshes/skins=2
|
|
||||||
blender/meshes/export_bones_deforming_mesh_only=false
|
|
||||||
blender/materials/unpack_enabled=true
|
|
||||||
blender/materials/export_materials=1
|
|
||||||
blender/animation/limit_playback=true
|
|
||||||
blender/animation/always_sample=true
|
|
||||||
blender/animation/group_tracks=true
|
|
||||||
gltf/naming_version=2
|
|
||||||
gltf/texture_map_mode=0
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://dj4x6k2phqsas"
|
||||||
|
path="res://.godot/imported/ball.glb-54e57a8d163cfef28e5e007118be03c5.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ball.glb"
|
||||||
|
dest_files=["res://.godot/imported/ball.glb-54e57a8d163cfef28e5e007118be03c5.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,59 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="scene"
|
|
||||||
importer_version=1
|
|
||||||
type="PackedScene"
|
|
||||||
uid="uid://cpawiqbrjyip5"
|
|
||||||
path="res://.godot/imported/gold_ball.glb-704aa0b9ad2b63d580121e20c4482485.scn"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/models/gold_ball.glb"
|
|
||||||
dest_files=["res://.godot/imported/gold_ball.glb-704aa0b9ad2b63d580121e20c4482485.scn"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
nodes/root_type="Node3D"
|
|
||||||
nodes/root_name="Scene Root"
|
|
||||||
nodes/root_script=null
|
|
||||||
mesh_library/use_node_names_as_mesh_names=false
|
|
||||||
array_mesh/deduplicate_surfaces=true
|
|
||||||
nodes/apply_root_scale=true
|
|
||||||
nodes/root_scale=1.0
|
|
||||||
nodes/import_as_skeleton_bones=false
|
|
||||||
nodes/use_name_suffixes=true
|
|
||||||
nodes/use_node_type_suffixes=true
|
|
||||||
meshes/ensure_tangents=true
|
|
||||||
meshes/generate_lods=true
|
|
||||||
meshes/create_shadow_meshes=true
|
|
||||||
meshes/light_baking=1
|
|
||||||
meshes/lightmap_texel_size=0.2
|
|
||||||
meshes/force_disable_compression=false
|
|
||||||
skins/use_named_skins=true
|
|
||||||
animation/import=true
|
|
||||||
animation/fps=30
|
|
||||||
animation/trimming=false
|
|
||||||
animation/remove_immutable_tracks=true
|
|
||||||
animation/import_rest_as_RESET=false
|
|
||||||
import_script/path=""
|
|
||||||
materials/extract=0
|
|
||||||
materials/extract_format=0
|
|
||||||
materials/extract_path=""
|
|
||||||
_subresources={
|
|
||||||
"meshes": {
|
|
||||||
"gold_ball_Sphere": {
|
|
||||||
"generate/lightmap_uv": 0,
|
|
||||||
"generate/lods": 0,
|
|
||||||
"generate/shadow_meshes": 0,
|
|
||||||
"lods/normal_merge_angle": 60.0,
|
|
||||||
"lods/normal_split_angle": 25.0,
|
|
||||||
"save_to_file/enabled": true,
|
|
||||||
"save_to_file/fallback_path": "res://assets/models/gold_ball.res",
|
|
||||||
"save_to_file/make_streamable": "",
|
|
||||||
"save_to_file/path": "uid://ucw1lyo43yi4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gltf/naming_version=0
|
|
||||||
gltf/embedded_image_handling=1
|
|
||||||
gltf/texture_map_mode=0
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://fc85v6374c1"
|
||||||
|
path="res://.godot/imported/nebula_debris.glb-39af77a17c0998b5b73172577d906cb9.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/nebula_debris.glb"
|
||||||
|
dest_files=["res://.godot/imported/nebula_debris.glb-39af77a17c0998b5b73172577d906cb9.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bpfwv7fse77wk"
|
||||||
|
path="res://.godot/imported/nebula_planet.glb-2431590907ff85cf1057e7d6ad614ed7.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/nebula_planet.glb"
|
||||||
|
dest_files=["res://.godot/imported/nebula_planet.glb-2431590907ff85cf1057e7d6ad614ed7.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 985 KiB |
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cr3feyv7yldjc"
|
||||||
|
path.s3tc="res://.godot/imported/nebula_planet_planet_surface.png-4b68cca75d2d22d25510c7eb277c0b85.s3tc.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "44a2e26010b529339e264deecde957e0"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/nebula_planet_planet_surface.png"
|
||||||
|
dest_files=["res://.godot/imported/nebula_planet_planet_surface.png-4b68cca75d2d22d25510c7eb277c0b85.s3tc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://dn8tj3iw0io7e"
|
||||||
|
path="res://.godot/imported/nebula_station.glb-fa9a6dd87ae3789d04205b52215e2e76.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/nebula_station.glb"
|
||||||
|
dest_files=["res://.godot/imported/nebula_station.glb-fa9a6dd87ae3789d04205b52215e2e76.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://dqsruccgu70n6"
|
||||||
|
path="res://.godot/imported/ship_canopy.glb-eb012be4d2aaadd3ad4ce782ee3cac26.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ship_canopy.glb"
|
||||||
|
dest_files=["res://.godot/imported/ship_canopy.glb-eb012be4d2aaadd3ad4ce782ee3cac26.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://3kf7d62qivgv"
|
||||||
|
path="res://.godot/imported/ship_engine_l.glb-36865e4bdf2ded2bf550380b5e5a31da.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ship_engine_l.glb"
|
||||||
|
dest_files=["res://.godot/imported/ship_engine_l.glb-36865e4bdf2ded2bf550380b5e5a31da.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://w3ps73chyt2t"
|
||||||
|
path="res://.godot/imported/ship_engine_r.glb-214ece30da53490ddc1cb491c37e947a.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ship_engine_r.glb"
|
||||||
|
dest_files=["res://.godot/imported/ship_engine_r.glb-214ece30da53490ddc1cb491c37e947a.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://31h3nccel10k"
|
||||||
|
path="res://.godot/imported/ship_hull.glb-e35cc22e21d218aa71b911f28b0dd14f.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ship_hull.glb"
|
||||||
|
dest_files=["res://.godot/imported/ship_hull.glb-e35cc22e21d218aa71b911f28b0dd14f.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://yio14umwtku1"
|
||||||
|
path="res://.godot/imported/ship_nose.glb-84e1b2cefdde564b2174201282e01d4e.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ship_nose.glb"
|
||||||
|
dest_files=["res://.godot/imported/ship_nose.glb-84e1b2cefdde564b2174201282e01d4e.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://c1i2lsx58u146"
|
||||||
|
path="res://.godot/imported/ship_tailfin.glb-d9d8f37674e2cf04cd3606e5c7a5c641.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/ship_tailfin.glb"
|
||||||
|
dest_files=["res://.godot/imported/ship_tailfin.glb-d9d8f37674e2cf04cd3606e5c7a5c641.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
mesh_library/use_node_names_as_mesh_names=false
|
||||||
|
array_mesh/deduplicate_surfaces=true
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
|
gltf/texture_map_mode=1
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://damajpvo00prm"
|
||||||
|
path.s3tc="res://.godot/imported/particle_glow.png-e6c07ae4c700896e29ebe96adf51fa5c.s3tc.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/textures/particle_glow.png"
|
||||||
|
dest_files=["res://.godot/imported/particle_glow.png-e6c07ae4c700896e29ebe96adf51fa5c.s3tc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 MiB |
@@ -0,0 +1,41 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dc445qysyxwwv"
|
||||||
|
path.bptc="res://.godot/imported/sky_nebula.png-70f974ab520aaa5206eef67a936b662e.bptc.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/textures/sky_nebula.png"
|
||||||
|
dest_files=["res://.godot/imported/sky_nebula.png-70f974ab520aaa5206eef67a936b662e.bptc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=true
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,115 @@
|
|||||||
|
[preset.0]
|
||||||
|
|
||||||
|
name="Linux Training"
|
||||||
|
platform="Linux"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features="training"
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="../training/build/CosmicClash.x86_64"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_encryption_key=""
|
||||||
|
|
||||||
|
[preset.0.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_script=1
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/bptc=true
|
||||||
|
texture_format/s3tc=true
|
||||||
|
texture_format/etc=false
|
||||||
|
texture_format/etc2=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
|
||||||
|
[preset.1]
|
||||||
|
|
||||||
|
name="Linux Dedicated Server"
|
||||||
|
platform="Linux"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=true
|
||||||
|
custom_features="dedicated_server"
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="../server/build/CosmicClashServer.x86_64"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_encryption_key=""
|
||||||
|
|
||||||
|
[preset.1.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_script=1
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/bptc=false
|
||||||
|
texture_format/s3tc=false
|
||||||
|
texture_format/etc=false
|
||||||
|
texture_format/etc2=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
|
||||||
|
[preset.2]
|
||||||
|
|
||||||
|
name="Linux Steam Client"
|
||||||
|
platform="Linux"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features="steam"
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="../steam/build/CosmicClashSteam.x86_64"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_encryption_key=""
|
||||||
|
|
||||||
|
[preset.2.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_script=1
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/bptc=true
|
||||||
|
texture_format/s3tc=true
|
||||||
|
texture_format/etc=false
|
||||||
|
texture_format/etc2=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
|
||||||
|
[preset.3]
|
||||||
|
|
||||||
|
name="Linux Steam Dedicated Server"
|
||||||
|
platform="Linux"
|
||||||
|
runnable=true
|
||||||
|
dedicated_server=true
|
||||||
|
custom_features="dedicated_server,steam"
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="../steam/build/CosmicClashSteamServer.x86_64"
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_encryption_key=""
|
||||||
|
|
||||||
|
[preset.3.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_script=1
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/bptc=false
|
||||||
|
texture_format/s3tc=false
|
||||||
|
texture_format/etc=false
|
||||||
|
texture_format/etc2=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
+1188
-75
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://27u3tdc5yqnl"]
|
[gd_scene load_steps=5 format=3 uid="uid://27u3tdc5yqnl"]
|
||||||
|
|
||||||
[ext_resource type="ArrayMesh" uid="uid://ucw1lyo43yi4" path="res://assets/models/gold_ball.res" id="1_ct1s3"]
|
[ext_resource type="ArrayMesh" path="res://assets/models/ball.res" id="1_ball"]
|
||||||
[ext_resource type="Script" path="res://scripts/ball.gd" id="2_ball"]
|
[ext_resource type="Script" path="res://scripts/ball.gd" id="2_ball"]
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_c5p07"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_c5p07"]
|
||||||
@@ -10,9 +10,12 @@ bounce = 0.5
|
|||||||
friction = 0.4
|
friction = 0.4
|
||||||
|
|
||||||
[node name="Ball" type="RigidBody3D" groups=["ball"]]
|
[node name="Ball" type="RigidBody3D" groups=["ball"]]
|
||||||
|
collision_layer = 2
|
||||||
|
collision_mask = 13
|
||||||
mass = 3
|
mass = 3
|
||||||
physics_material_override = SubResource("PhysicsMaterial_ball")
|
physics_material_override = SubResource("PhysicsMaterial_ball")
|
||||||
continuous_cd = true
|
continuous_cd = true
|
||||||
|
can_sleep = false
|
||||||
inertia = Vector3(3, 3, 3)
|
inertia = Vector3(3, 3, 3)
|
||||||
gravity_scale = 0.8
|
gravity_scale = 0.8
|
||||||
linear_damp = 0.1
|
linear_damp = 0.1
|
||||||
@@ -23,6 +26,8 @@ metadata/_edit_group_ = true
|
|||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
shape = SubResource("SphereShape3D_c5p07")
|
shape = SubResource("SphereShape3D_c5p07")
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
[node name="Visual" type="Node3D" parent="."]
|
||||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 2.08165e-12, 2.08165e-12, 2.08165e-12)
|
|
||||||
mesh = ExtResource("1_ct1s3")
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="Visual"]
|
||||||
|
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0)
|
||||||
|
mesh = ExtResource("1_ball")
|
||||||
|
|||||||
+3
-12
@@ -1,24 +1,15 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://cofdcxo5170rs"]
|
[gd_scene load_steps=3 format=3 uid="uid://cofdcxo5170rs"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/goal.gd" id="1_v8ikr"]
|
[ext_resource type="Script" path="res://scripts/goal.gd" id="1_v8ikr"]
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ptddx"]
|
|
||||||
transparency = 1
|
|
||||||
albedo_color = Color(0, 0.368627, 1, 0.537255)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_4ngif"]
|
|
||||||
material = SubResource("StandardMaterial3D_ptddx")
|
|
||||||
size = Vector3(3.5, 1.5, 0.1)
|
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_nmd5r"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_nmd5r"]
|
||||||
size = Vector3(3.5, 1.5, 0.1)
|
size = Vector3(3.5, 1.5, 0.1)
|
||||||
|
|
||||||
[node name="Goal" type="Area3D"]
|
[node name="Goal" type="Area3D"]
|
||||||
|
collision_layer = 8
|
||||||
|
collision_mask = 2
|
||||||
script = ExtResource("1_v8ikr")
|
script = ExtResource("1_v8ikr")
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
|
||||||
mesh = SubResource("BoxMesh_4ngif")
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
shape = SubResource("BoxShape3D_nmd5r")
|
shape = SubResource("BoxShape3D_nmd5r")
|
||||||
|
|
||||||
|
|||||||
+15
-66
@@ -1,86 +1,35 @@
|
|||||||
[gd_scene load_steps=13 format=3 uid="uid://p07epxnh8wwp"]
|
[gd_scene load_steps=13 format=3 uid="uid://p07epxnh8wwp"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dyq1n7q1bqjps" path="res://scripts/ship.gd" id="1_efag7"]
|
[ext_resource type="Script" uid="uid://dyq1n7q1bqjps" path="res://scripts/ship.gd" id="1_efag7"]
|
||||||
|
[ext_resource type="ArrayMesh" path="res://assets/models/ship_nose.res" id="3_nose"]
|
||||||
|
[ext_resource type="ArrayMesh" path="res://assets/models/ship_tailfin.res" id="5_talfin"]
|
||||||
|
|
||||||
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_ship"]
|
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_ship"]
|
||||||
friction = 0.1
|
friction = 0.1
|
||||||
bounce = 0.15
|
bounce = 0.15
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hull"]
|
|
||||||
albedo_color = Color(0.35, 0.37, 0.42, 1)
|
|
||||||
metallic = 0.6
|
|
||||||
roughness = 0.4
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_accent"]
|
|
||||||
albedo_color = Color(0.25, 0.55, 1, 1)
|
|
||||||
metallic = 0.3
|
|
||||||
roughness = 0.5
|
|
||||||
emission_enabled = true
|
|
||||||
emission = Color(0.25, 0.55, 1, 1)
|
|
||||||
emission_energy_multiplier = 0.35
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_canopy"]
|
|
||||||
albedo_color = Color(0.15, 0.85, 1, 1)
|
|
||||||
metallic = 0.8
|
|
||||||
roughness = 0.1
|
|
||||||
emission_enabled = true
|
|
||||||
emission = Color(0.15, 0.85, 1, 1)
|
|
||||||
emission_energy_multiplier = 0.5
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_engine"]
|
|
||||||
albedo_color = Color(1, 0.55, 0.15, 1)
|
|
||||||
emission_enabled = true
|
|
||||||
emission = Color(1, 0.55, 0.15, 1)
|
|
||||||
emission_energy_multiplier = 2.0
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_hull"]
|
|
||||||
material = SubResource("StandardMaterial3D_hull")
|
|
||||||
size = Vector3(1, 0.75, 2.7)
|
|
||||||
|
|
||||||
[sub_resource type="PrismMesh" id="PrismMesh_nose"]
|
|
||||||
material = SubResource("StandardMaterial3D_accent")
|
|
||||||
size = Vector3(1, 1.4, 0.75)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_canopy"]
|
|
||||||
material = SubResource("StandardMaterial3D_canopy")
|
|
||||||
size = Vector3(0.55, 0.28, 0.8)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_fin"]
|
|
||||||
material = SubResource("StandardMaterial3D_accent")
|
|
||||||
size = Vector3(0.1, 0.5, 0.8)
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_engine"]
|
|
||||||
material = SubResource("StandardMaterial3D_engine")
|
|
||||||
size = Vector3(0.7, 0.5, 0.25)
|
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_dsjou"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_dsjou"]
|
||||||
size = Vector3(1, 1, 4)
|
size = Vector3(1.6, 0.6, 4)
|
||||||
|
|
||||||
[node name="Ship" type="RigidBody3D"]
|
[node name="Ship" type="RigidBody3D"]
|
||||||
|
collision_layer = 1
|
||||||
|
collision_mask = 7
|
||||||
mass = 5.0
|
mass = 5.0
|
||||||
physics_material_override = SubResource("PhysicsMaterial_ship")
|
physics_material_override = SubResource("PhysicsMaterial_ship")
|
||||||
inertia = Vector3(1, 1, 1)
|
inertia = Vector3(7, 1, 7)
|
||||||
|
can_sleep = false
|
||||||
|
continuous_cd = true
|
||||||
script = ExtResource("1_efag7")
|
script = ExtResource("1_efag7")
|
||||||
|
|
||||||
[node name="Hull" type="MeshInstance3D" parent="."]
|
[node name="Visual" type="Node3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.125, 0.35)
|
|
||||||
mesh = SubResource("BoxMesh_hull")
|
|
||||||
|
|
||||||
[node name="Nose" type="MeshInstance3D" parent="."]
|
[node name="Nose" type="MeshInstance3D" parent="Visual"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, -0.125, -1.3)
|
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0)
|
||||||
mesh = SubResource("PrismMesh_nose")
|
mesh = ExtResource("3_nose")
|
||||||
|
|
||||||
[node name="Canopy" type="MeshInstance3D" parent="."]
|
[node name="TailFin" type="MeshInstance3D" parent="Visual"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.36, -0.5)
|
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.24, 0.72)
|
||||||
mesh = SubResource("BoxMesh_canopy")
|
mesh = ExtResource("5_talfin")
|
||||||
|
|
||||||
[node name="TailFin" type="MeshInstance3D" parent="."]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, 1.25)
|
|
||||||
mesh = SubResource("BoxMesh_fin")
|
|
||||||
|
|
||||||
[node name="EngineGlow" type="MeshInstance3D" parent="."]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.125, 1.85)
|
|
||||||
mesh = SubResource("BoxMesh_engine")
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
shape = SubResource("BoxShape3D_dsjou")
|
shape = SubResource("BoxShape3D_dsjou")
|
||||||
|
|||||||
+87
-22
@@ -20,10 +20,23 @@ config/version="0.0.1"
|
|||||||
run/main_scene="uid://bcq14356s3e2i"
|
run/main_scene="uid://bcq14356s3e2i"
|
||||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
run/main_scene.training="res://scenes/training.tscn"
|
||||||
|
run/main_scene.dedicated_server="res://scenes/server_boot.tscn"
|
||||||
|
|
||||||
[editor_plugins]
|
[autoload]
|
||||||
|
|
||||||
enabled=PackedStringArray("res://addons/godot_rl_agents/plugin.cfg")
|
GameSettings="*res://scripts/game_settings.gd"
|
||||||
|
ControlPlaneClient="*res://scripts/control_plane_client.gd"
|
||||||
|
VideoSettings="*res://scripts/video_settings.gd"
|
||||||
|
InputSettings="*res://scripts/input_settings.gd"
|
||||||
|
BackgroundFPS="*res://scripts/background_fps.gd"
|
||||||
|
PerfOverlay="*res://scripts/perf_overlay.gd"
|
||||||
|
NetSim="*res://scripts/net_sim.gd"
|
||||||
|
NetworkManager="*res://scripts/network_manager.gd"
|
||||||
|
MatchNet="*res://scripts/match_net.gd"
|
||||||
|
MatchSim="*res://scripts/match_sim.gd"
|
||||||
|
NetDebugOverlay="*res://scripts/net_debug_overlay.gd"
|
||||||
|
AudioManager="*res://scripts/audio_manager.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
@@ -32,45 +45,60 @@ window/size/viewport_height=1080
|
|||||||
window/size/mode=2
|
window/size/mode=2
|
||||||
window/stretch/mode="viewport"
|
window/stretch/mode="viewport"
|
||||||
window/stretch/aspect="expand"
|
window/stretch/aspect="expand"
|
||||||
|
window/vsync/vsync_mode=2
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/godot_rl_agents/plugin.cfg")
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
reset_ball={"deadzone": 0.5, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)]}
|
reset_ball={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
move_forward={
|
move_forward={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_back={
|
move_back={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_left={
|
move_left={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_right={
|
move_right={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_up={
|
move_up={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_down={
|
move_down={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
turbo={
|
turbo={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
turn_left={
|
turn_left={
|
||||||
@@ -88,13 +116,13 @@ turn_right={
|
|||||||
pitch_up={
|
pitch_up={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
pitch_down={
|
pitch_down={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
roll_left={
|
roll_left={
|
||||||
@@ -109,24 +137,61 @@ roll_right={
|
|||||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
toggle_ball_cam={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":8,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ui_cancel={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ui_accept={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194310,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
leave_gameplay={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
toggle_perf_overlay={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194334,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
toggle_net_overlay={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194335,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
[layer_names]
|
||||||
|
|
||||||
|
3d_physics/layer_1/name="Ships"
|
||||||
|
3d_physics/layer_2/name="Ball"
|
||||||
|
3d_physics/layer_3/name="Arena"
|
||||||
|
3d_physics/layer_4/name="GoalSensor"
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
common/physics_interpolation=true
|
common/physics_jitter_fix=0.0
|
||||||
3d/physics_engine="Jolt Physics"
|
3d/physics_engine="Jolt Physics"
|
||||||
|
common/physics_interpolation=true
|
||||||
|
|
||||||
[autoload]
|
[rendering]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GameSettings="*res://scripts/game_settings.gd"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lights_and_shadows/directional_shadow/size=2048
|
||||||
|
anti_aliasing/quality/msaa_3d=2
|
||||||
|
anti_aliasing/quality/use_debanding=true
|
||||||
|
anti_aliasing/quality/screen_space_aa=1
|
||||||
|
lights_and_shadows/positional_shadow/atlas_size=2048
|
||||||
|
lights_and_shadows/soft_shadow_filter_quality=2
|
||||||
|
|||||||
+130
-32
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=6 format=3 uid="uid://c8kak2l3m4n5"]
|
[gd_scene load_steps=9 format=3 uid="uid://c8kak2l3m4n5"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://du7y176h5aaq4" path="res://scripts/HUDController.gd" id="1_hud_controller"]
|
[ext_resource type="Script" uid="uid://du7y176h5aaq4" path="res://scripts/HUDController.gd" id="1_hud_controller"]
|
||||||
[ext_resource type="Script" path="res://scripts/hud_attitude_indicator.gd" id="2_adi"]
|
[ext_resource type="Script" path="res://scripts/hud_attitude_indicator.gd" id="2_adi"]
|
||||||
@@ -8,6 +8,36 @@
|
|||||||
[sub_resource type="LabelSettings" id="LabelSettings_hud"]
|
[sub_resource type="LabelSettings" id="LabelSettings_hud"]
|
||||||
font_size = 32
|
font_size = 32
|
||||||
|
|
||||||
|
[sub_resource type="LabelSettings" id="LabelSettings_kickoff"]
|
||||||
|
font_size = 96
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_scoreboard"]
|
||||||
|
bg_color = Color(0.05, 0.06, 0.09, 0.6)
|
||||||
|
corner_radius_top_left = 10
|
||||||
|
corner_radius_top_right = 10
|
||||||
|
corner_radius_bottom_right = 10
|
||||||
|
corner_radius_bottom_left = 10
|
||||||
|
content_margin_left = 20.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 20.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_result"]
|
||||||
|
bg_color = Color(0.05, 0.06, 0.09, 0.85)
|
||||||
|
border_width_left = 3
|
||||||
|
border_width_top = 3
|
||||||
|
border_width_right = 3
|
||||||
|
border_width_bottom = 3
|
||||||
|
border_color = Color(1, 1, 1, 0.2)
|
||||||
|
corner_radius_top_left = 20
|
||||||
|
corner_radius_top_right = 20
|
||||||
|
corner_radius_bottom_right = 20
|
||||||
|
corner_radius_bottom_left = 20
|
||||||
|
content_margin_left = 48.0
|
||||||
|
content_margin_top = 32.0
|
||||||
|
content_margin_right = 48.0
|
||||||
|
content_margin_bottom = 32.0
|
||||||
|
|
||||||
[node name="HUD" type="CanvasLayer"]
|
[node name="HUD" type="CanvasLayer"]
|
||||||
process_mode = 3
|
process_mode = 3
|
||||||
script = ExtResource("1_hud_controller")
|
script = ExtResource("1_hud_controller")
|
||||||
@@ -21,46 +51,109 @@ grow_horizontal = 2
|
|||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
||||||
[node name="TimerLabel" type="Label" parent="Control"]
|
[node name="ScoreboardPanel" type="PanelContainer" parent="Control"]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 5
|
anchors_preset = 5
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -90.5
|
offset_left = -220.0
|
||||||
offset_right = 90.5
|
offset_right = 220.0
|
||||||
offset_bottom = 62.0
|
offset_top = 8.0
|
||||||
|
offset_bottom = 60.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_scoreboard")
|
||||||
|
|
||||||
|
[node name="ScoreRow" type="HBoxContainer" parent="Control/ScoreboardPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
|
||||||
|
[node name="Team0Name" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Team0Score" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
horizontal_alignment = 1
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="TimerLabel" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
label_settings = SubResource("LabelSettings_hud")
|
label_settings = SubResource("LabelSettings_hud")
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="ScoreRow" type="HBoxContainer" parent="Control"]
|
[node name="Team1Score" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
horizontal_alignment = 1
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="Team1Name" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="KickoffLabel" type="Label" parent="Control"]
|
||||||
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 5
|
anchors_preset = 8
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -90.5
|
anchor_bottom = 0.5
|
||||||
offset_right = 90.5
|
offset_left = -90.0
|
||||||
offset_top = 64.0
|
offset_top = -60.0
|
||||||
offset_bottom = 110.0
|
offset_right = 90.0
|
||||||
|
offset_bottom = 60.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
alignment = 1
|
grow_vertical = 2
|
||||||
|
label_settings = SubResource("LabelSettings_kickoff")
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
[node name="Team0Score" type="Label" parent="Control/ScoreRow"]
|
[node name="GoalCelebration" type="Control" parent="Control"]
|
||||||
layout_mode = 2
|
visible = false
|
||||||
theme_override_colors/font_color = Color(0.25, 0.55, 1, 1)
|
layout_mode = 1
|
||||||
theme_override_font_sizes/font_size = 32
|
anchors_preset = 15
|
||||||
text = "0"
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
[node name="ScoreDash" type="Label" parent="Control/ScoreRow"]
|
[node name="Tint" type="ColorRect" parent="Control/GoalCelebration"]
|
||||||
layout_mode = 2
|
layout_mode = 1
|
||||||
theme_override_font_sizes/font_size = 32
|
anchors_preset = 15
|
||||||
text = " - "
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
[node name="Team1Score" type="Label" parent="Control/ScoreRow"]
|
[node name="Center" type="CenterContainer" parent="Control/GoalCelebration"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="GoalTitle" type="Label" parent="Control/GoalCelebration/Center"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(1, 0.5, 0.15, 1)
|
theme_override_colors/font_shadow_color = Color(0, 0, 0, 0.8)
|
||||||
theme_override_font_sizes/font_size = 32
|
theme_override_constants/shadow_offset_x = 5
|
||||||
text = "0"
|
theme_override_constants/shadow_offset_y = 5
|
||||||
|
theme_override_font_sizes/font_size = 82
|
||||||
|
text = "GOAL!"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="ResultOverlay" type="Control" parent="Control"]
|
[node name="ResultOverlay" type="Control" parent="Control"]
|
||||||
visible = false
|
visible = false
|
||||||
@@ -88,23 +181,28 @@ anchor_bottom = 1.0
|
|||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
|
|
||||||
[node name="VBox" type="VBoxContainer" parent="Control/ResultOverlay/Center"]
|
[node name="ResultPanel" type="PanelContainer" parent="Control/ResultOverlay/Center"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_result")
|
||||||
|
|
||||||
|
[node name="VBox" type="VBoxContainer" parent="Control/ResultOverlay/Center/ResultPanel"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 16
|
theme_override_constants/separation = 16
|
||||||
|
|
||||||
[node name="ResultLabel" type="Label" parent="Control/ResultOverlay/Center/VBox"]
|
[node name="ResultLabel" type="Label" parent="Control/ResultOverlay/Center/ResultPanel/VBox"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 56
|
theme_override_font_sizes/font_size = 64
|
||||||
text = "Draw"
|
text = "Draw"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="FinalScoreLabel" type="Label" parent="Control/ResultOverlay/Center/VBox"]
|
[node name="FinalScoreLabel" type="Label" parent="Control/ResultOverlay/Center/ResultPanel/VBox"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 36
|
theme_override_font_sizes/font_size = 44
|
||||||
text = "0 - 0"
|
text = "0 - 0"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="Instruments" type="Control" parent="Control"]
|
[node name="Instruments" type="Control" parent="Control"]
|
||||||
|
process_mode = 1
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -120,8 +218,8 @@ anchor_left = 0.5
|
|||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -180.0
|
offset_left = -180.0
|
||||||
offset_right = 180.0
|
offset_right = 180.0
|
||||||
offset_top = 116.0
|
offset_top = 68.0
|
||||||
offset_bottom = 156.0
|
offset_bottom = 108.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
script = ExtResource("3_tape")
|
script = ExtResource("3_tape")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user